[arvados-workbench2] updated: 2.7.0-137-g58afb8df

git repository hosting git at public.arvados.org
Tue Oct 10 19:31:37 UTC 2023


Summary of changes:
 Makefile                                           |   30 +-
 cypress/integration/collection.spec.js             |  258 ++-
 cypress/integration/create-workflow.spec.js        |    2 +-
 cypress/integration/process.spec.js                | 2228 ++++++++++----------
 cypress/integration/project.spec.js                |    6 +-
 cypress/integration/search.spec.js                 |   10 +-
 cypress/integration/sharing.spec.js                |   14 +
 cypress/integration/side-panel.spec.js             |    2 +-
 cypress/integration/user-profile.spec.js           |    5 +
 cypress/integration/virtual-machine-admin.spec.js  |    5 +-
 cypress/integration/workflow.spec.js               |   31 +
 cypress/support/commands.js                        |  302 ++-
 package.json                                       |   16 +-
 src/common/config.ts                               |    6 +-
 src/common/redirect-to.test.ts                     |    4 +-
 src/common/redirect-to.ts                          |    2 +-
 src/common/use-async-interval.test.tsx             |   96 +
 src/common/use-async-interval.ts                   |   45 +
 .../collection-panel-files.tsx                     |   59 +-
 .../confirmation-dialog/confirmation-dialog.tsx    |    4 +-
 src/components/data-explorer/data-explorer.tsx     |   53 +-
 src/components/dropdown-menu/dropdown-menu.tsx     |   22 +-
 src/components/icon/icon.tsx                       |  405 ++--
 src/components/tree/tree.tsx                       |  193 +-
 src/index.tsx                                      |   12 +-
 src/models/collection-file.ts                      |    6 +-
 src/models/container-request.ts                    |   63 +-
 src/models/group.ts                                |   21 +-
 src/models/log.ts                                  |    1 -
 src/models/project.ts                              |    3 +-
 src/models/test-utils.ts                           |    3 +-
 src/models/user.ts                                 |    3 +-
 src/models/workflow.ts                             |   29 +-
 src/routes/route-change-handlers.ts                |    5 +-
 .../ancestors-service/ancestors-service.ts         |   11 +-
 src/services/auth-service/auth-service.ts          |    4 +
 .../collection-service/collection-service.test.ts  |   30 +-
 .../collection-service/collection-service.ts       |  108 +-
 .../common-service/common-resource-service.ts      |   22 +-
 src/services/groups-service/groups-service.ts      |  125 +-
 src/services/log-service/log-service.test.ts       |  168 ++
 src/services/log-service/log-service.ts            |   51 +-
 src/services/services.ts                           |   22 +-
 src/services/user-service/user-service.ts          |    3 +-
 src/store/advanced-tab/advanced-tab.tsx            |    7 +-
 src/store/auth/auth-action.test.ts                 |    7 +-
 src/store/auth/auth-middleware.ts                  |   12 +-
 src/store/breadcrumbs/breadcrumbs-actions.ts       |   51 +-
 .../collection-panel-files-state.ts                |   23 +-
 ...llections-content-address-middleware-service.ts |    5 +-
 .../collections/collection-partial-copy-actions.ts |  253 ++-
 .../collections/collection-partial-move-actions.ts |  252 +++
 .../context-menu/context-menu-actions.test.ts      |    4 +-
 src/store/context-menu/context-menu-actions.ts     |    3 +-
 src/store/data-explorer/data-explorer-reducer.ts   |   26 +-
 .../process-logs-panel-actions.ts                  |  343 ++-
 .../process-logs-panel-reducer.ts                  |   29 +-
 src/store/process-logs-panel/process-logs-panel.ts |    6 +-
 src/store/processes/process.ts                     |    4 +
 src/store/processes/processes-actions.ts           |   19 +-
 src/store/project-panel/project-panel-action.ts    |    4 +-
 .../project-panel-middleware-service.ts            |   10 +-
 .../resource-type-filters.test.ts                  |    2 +-
 .../resource-type-filters/resource-type-filters.ts |    2 +-
 src/store/resources/resources.test.ts              |    9 +-
 src/store/resources/resources.ts                   |   30 +-
 .../run-process-panel-actions.test.ts              |    2 +-
 .../run-process-panel/run-process-panel-actions.ts |    4 +-
 .../shared-with-me-middleware-service.ts           |   17 +-
 src/store/store.ts                                 |  172 +-
 .../trash-panel/trash-panel-middleware-service.ts  |    2 +-
 src/store/tree-picker/tree-picker-actions.ts       |  132 +-
 src/store/tree-picker/tree-picker-middleware.ts    |  104 +-
 src/store/user-profile/user-profile-actions.ts     |  256 +--
 src/store/workbench/workbench-actions.ts           |    8 +-
 src/store/workflow-panel/workflow-panel-actions.ts |   14 +-
 src/views-components/baner/banner.tsx              |   78 +-
 .../action-sets/collection-files-action-set.ts     |  112 +-
 .../collection-files-item-action-set.ts            |   68 +-
 .../collection-files-not-selected-action-set.ts    |    4 +-
 .../action-sets/workflow-action-set.ts             |   45 +-
 src/views-components/context-menu/context-menu.tsx |    5 +-
 src/views-components/data-explorer/renderers.tsx   |  466 ++--
 ...ection-partial-copy-to-existing-collection.tsx} |   15 +-
 ...-collection-partial-copy-to-new-collection.tsx} |   12 +-
 ...ction-partial-copy-to-separate-collections.tsx} |   16 +-
 .../dialog-forms/partial-copy-collection-dialog.ts |   22 -
 .../partial-copy-to-collection-dialog.ts           |   21 -
 .../partial-copy-to-existing-collection-dialog.ts  |   21 +
 .../partial-copy-to-new-collection-dialog.ts       |   21 +
 .../partial-copy-to-separate-collections-dialog.ts |   21 +
 .../partial-move-to-existing-collection-dialog.ts  |   21 +
 .../partial-move-to-new-collection-dialog.ts       |   21 +
 .../partial-move-to-separate-collections-dialog.ts |   21 +
 ...lection-partial-move-to-existing-collection.tsx |   30 +
 ...-collection-partial-move-to-new-collection.tsx} |   14 +-
 ...ection-partial-move-to-separate-collections.tsx |   29 +
 .../form-fields/collection-form-fields.tsx         |   14 +-
 src/views-components/login-form/login-form.tsx     |  106 +-
 src/views-components/main-app-bar/account-menu.tsx |    6 +-
 .../main-app-bar/notifications-menu.tsx            |   57 +-
 .../projects-tree-picker/favorites-tree-picker.tsx |    6 +-
 .../generic-projects-tree-picker.tsx               |   11 +-
 .../projects-tree-picker/home-tree-picker.tsx      |    4 +-
 .../projects-tree-picker/projects-tree-picker.tsx  |    6 +-
 .../public-favorites-tree-picker.tsx               |    6 +-
 .../search-projects-picker.tsx                     |    4 +-
 .../projects-tree-picker/shared-tree-picker.tsx    |    4 +-
 .../projects-tree-picker/tree-picker-field.tsx     |   23 +
 .../sharing-dialog/sharing-dialog-component.tsx    |    4 +-
 .../sharing-dialog/sharing-invitation-form.tsx     |    3 -
 .../sharing-public-access-form-component.tsx       |    2 +-
 .../side-panel-button/side-panel-button.tsx        |    3 +-
 src/views/collection-panel/collection-panel.tsx    |   10 +-
 .../group-details-panel/group-details-panel.tsx    |   96 +-
 .../keep-service-panel/keep-service-panel-root.tsx |    8 +-
 .../process-panel/process-details-attributes.tsx   |   58 +-
 src/views/process-panel/process-details-card.tsx   |    4 +-
 src/views/process-panel/process-io-card.tsx        |  229 +-
 src/views/process-panel/process-log-card.tsx       |   11 +-
 .../process-panel/process-log-code-snippet.tsx     |    4 +-
 src/views/process-panel/process-panel-root.tsx     |    2 +
 src/views/process-panel/process-panel.tsx          |    3 +-
 src/views/process-panel/process-resource-card.tsx  |    3 +-
 .../repositories-panel/repositories-panel.tsx      |    6 +-
 src/views/run-process-panel/inputs/enum-input.tsx  |   36 +-
 .../run-process-panel/inputs/file-array-input.tsx  |    1 +
 src/views/run-process-panel/inputs/file-input.tsx  |    1 +
 .../run-process-panel/inputs/project-input.tsx     |    2 +-
 .../run-process-panel/run-process-inputs-form.tsx  |    6 +-
 src/views/ssh-key-panel/ssh-key-panel-root.tsx     |    6 +-
 .../user-profile-panel/user-profile-panel-root.tsx |   45 +-
 .../user-profile-panel/user-profile-panel.tsx      |   28 +-
 .../virtual-machine-admin-panel.tsx                |    4 +-
 src/views/workbench/workbench.tsx                  |   37 +-
 .../workflow-panel/registered-workflow-panel.tsx   |    4 +-
 src/websocket/websocket.ts                         |   37 +-
 tools/run-integration-tests.sh                     |    4 +-
 tsconfig.json                                      |    1 +
 yarn.lock                                          | 1491 ++++++++-----
 140 files changed, 6297 insertions(+), 3462 deletions(-)
 create mode 100644 src/common/use-async-interval.test.tsx
 create mode 100644 src/common/use-async-interval.ts
 create mode 100644 src/services/log-service/log-service.test.ts
 create mode 100644 src/store/collections/collection-partial-move-actions.ts
 copy src/views-components/dialog-copy/{dialog-partial-copy-to-collection.tsx => dialog-collection-partial-copy-to-existing-collection.tsx} (63%)
 copy src/views-components/dialog-copy/{dialog-collection-partial-copy.tsx => dialog-collection-partial-copy-to-new-collection.tsx} (68%)
 rename src/views-components/dialog-copy/{dialog-partial-copy-to-collection.tsx => dialog-collection-partial-copy-to-separate-collections.tsx} (55%)
 delete mode 100644 src/views-components/dialog-forms/partial-copy-collection-dialog.ts
 delete mode 100644 src/views-components/dialog-forms/partial-copy-to-collection-dialog.ts
 create mode 100644 src/views-components/dialog-forms/partial-copy-to-existing-collection-dialog.ts
 create mode 100644 src/views-components/dialog-forms/partial-copy-to-new-collection-dialog.ts
 create mode 100644 src/views-components/dialog-forms/partial-copy-to-separate-collections-dialog.ts
 create mode 100644 src/views-components/dialog-forms/partial-move-to-existing-collection-dialog.ts
 create mode 100644 src/views-components/dialog-forms/partial-move-to-new-collection-dialog.ts
 create mode 100644 src/views-components/dialog-forms/partial-move-to-separate-collections-dialog.ts
 create mode 100644 src/views-components/dialog-move/dialog-collection-partial-move-to-existing-collection.tsx
 rename src/views-components/{dialog-copy/dialog-collection-partial-copy.tsx => dialog-move/dialog-collection-partial-move-to-new-collection.tsx} (59%)
 create mode 100644 src/views-components/dialog-move/dialog-collection-partial-move-to-separate-collections.tsx

       via  58afb8dfcfa3fefa8e1b28dc418b97c51a73b7dc (commit)
       via  e10e24d713e5c31cbc34efca458f5718eb6eb000 (commit)
       via  c30fc0c76112671804c2bab41c561f1a6f381920 (commit)
       via  9ecdc799a7f75f971c5d79aa812b7d75686c413a (commit)
       via  0dc881dfb7cc567f27429686f972ba0bf877d651 (commit)
       via  840619fe8363099a9222b2cdda2d9e5d266a5801 (commit)
       via  c2ba0170975fd01e2b3d9229b491ef2dc8c2f010 (commit)
       via  345b5f9d1bae7ee8cda5383d4ac9abea6b54ca51 (commit)
       via  0ccf8be1713e2a21ad4214d6f64913d1a9d8c8e7 (commit)
       via  f79ee597833b880551baff9aef059919d80d5cd7 (commit)
       via  1a38a6a9a715f0fca6d2f53e38ddd3b338e1bfe0 (commit)
       via  6f8dcb2b13f3058db656908fb26b09e23b527f08 (commit)
       via  9acf0accecfba4ac2f6dd2bb5f441ba7354a237b (commit)
       via  0737f5067e29dea6a72a6612fddffe64d919e459 (commit)
       via  b4f14437823e9201e9cd952a4f6f3b1502b583bd (commit)
       via  ed1e56fa432068125967ad3128d224153c157f25 (commit)
       via  121165a587c43edfab0431aa1685dd8975a71e88 (commit)
       via  5afc1f6ee27b2135827d0f2976ed01ad0534ba62 (commit)
       via  2ec9ac24aca0a5f05c91ef4ed0cefc40312e8515 (commit)
       via  a54b7454b45a08aae06cdf66082c69ae63bd8dc4 (commit)
       via  dab6ac81df3cb99f1eb0edbddc07d731dfd61eb1 (commit)
       via  07d08ce946a4c52dba62024f6682c3eceb87b3c2 (commit)
       via  f1b1d63cd6dcb29951e456e487db7d7afdeafa69 (commit)
       via  e246c6f8bed015938ef871daf1a0e6f157311adf (commit)
       via  91e862a5653e9f8eae5c7cc4928009d7b7a27dc2 (commit)
       via  a95ab07460b4999e0e00fd6d1ac9358b009ebd03 (commit)
       via  d8b6d772739992431b81de993f8adc783b00feb1 (commit)
       via  017ab19b42969ed662d11291d399294c8f231a4e (commit)
       via  c48418079ce922e7f0710b552a5f13280dd1e4b5 (commit)
       via  74e6a2456fbff6a8e58adde0f4a3df6eb475802b (commit)
       via  2f6916cd8bbf45eaf4b35a7534c2e2b4433b6fb2 (commit)
       via  e16790d4df1a6643d1034e535463ccbd7b2710bc (commit)
       via  d0c19f9eb52b9230d176a6c6e9e7e941c89d9e05 (commit)
       via  c59724a9e5d4c9241df58d9d60541a63f0f06b80 (commit)
       via  00f1d0aecf676f50a27ed25dff974cfda23160c1 (commit)
       via  ceb9037bd7da9d2c0352c09184e07096db982046 (commit)
       via  606957c578a59510a4fafac6568477c06de43606 (commit)
       via  39c55ead904f943894f1658b9efa4a7c77584382 (commit)
       via  a6663da8ad5471d200197a777af98b1e1d9db86c (commit)
       via  ac6562028c3b71210988b4f845ac2945f4e88ace (commit)
       via  3cd7b7d270c0dc66df2ada32cf17d37efecc3feb (commit)
       via  5a0fee4d900804c50149cc87376e0bc65a390985 (commit)
       via  cf44d56c363942f97041e475e4bb29d8918482a3 (commit)
       via  d5e01ee547a97d506828962823cdaa6445869a18 (commit)
       via  0ab0f06d5f26c28b58a4e4fa6bc91753cccefd81 (commit)
       via  d4e77383fbd444a12152e575688b45c369104d53 (commit)
       via  2d595a358546ba3da4f0222310bdd5bc1dfdda3e (commit)
       via  03dbc22677f926e3480d2471fc85d02444d46a5e (commit)
       via  655f8715571191c6b189e6d92aebc5e91d3547fe (commit)
       via  6159c98c7ece2f9e0291482dba4b62e208b87343 (commit)
       via  408317d750873713eeac665f15da48389f7dc4bc (commit)
       via  3332d2f522adba7f57590f95eef88f68e90ff82c (commit)
       via  20b17ce2416339b68b598765ed1f7f09e60cd95f (commit)
       via  dae4909992be2098974ef6cd067ecab616ed8a56 (commit)
       via  89d84fd0b6d998e0b7a6aae63ddb96fb6e29ed42 (commit)
       via  71b40d027ef53530643e1b2a81eb276f16d7ec85 (commit)
       via  59cc53cbed401e6b5ad750d992f64a8f5c83c6bf (commit)
       via  b0e34765f018db004425e274c0ce9e58b7a184b5 (commit)
       via  038d4d4f8ecf66949b321c4ece4aa794719f9f3e (commit)
       via  940cbe92c1688e52c8741f8cb85bea4c3ced6756 (commit)
       via  4a389adfd855075773d7b5419891cfecd3d9b5dc (commit)
       via  587195ac34ceebc8ce8be5320658926cd1e5dc44 (commit)
       via  7a0091597196ff8fbce0495607771b8688ffea46 (commit)
       via  12ce690f0f6bede266f497131f8479d8343ae3bd (commit)
       via  df0c1e74137bfbd9263da9966ab9d171af5aba07 (commit)
       via  8dbd060cdc504973ca88a8bf806dc7eb3f8df31b (commit)
       via  f6f88d9ca9cdeeeebfadcfe999789bfb9f69e5c6 (commit)
       via  28d2b693f71febe45d75246c379b8d192b05018c (commit)
       via  9b64a168b39ccabc9f953163b289e8b017c7bc1f (commit)
       via  32e9b073f9a153a02cecd6adb902b3bfa36e2ff3 (commit)
       via  abc9ef71a341f72c061c6fc8b0276fcf9937c411 (commit)
       via  30451e35b3d214f300fd4124e4d5e3fee69665a2 (commit)
       via  d53a08ea0cc9a9af1a3071969d41165e8a6ff5d5 (commit)
       via  05acb61d6369592b987fa827cac3e5774d1a7b1e (commit)
       via  75eb080fec293c283ce934c5c82cfeee85bee4c1 (commit)
       via  33b725526a0a16c6006d1ceaa89039d263024bc1 (commit)
       via  41779cd087575a8216450e8dd27b20ef0fb13dd5 (commit)
       via  5430c336b96cbb7c20bffa1cbdb8cffea32fb460 (commit)
       via  756e585f2c1c50ed550d88c7becfe757dd9d8401 (commit)
       via  a5c8f799e1bd90a3d7a61d2c88215bf380508d84 (commit)
       via  d76aa6ba31074f3d41cef48628b235aab4906ce5 (commit)
       via  ce4b1330e232f9f29b6f0bf361f3fd6913d9ae6e (commit)
       via  84161e969c13b42451752bd5f134b968d15c1001 (commit)
       via  eaf099867227a9d5198a638205bdcddde7c17965 (commit)
       via  cb89000d480757f8e0f97f3560c5942d08bcd95a (commit)
       via  9a62117dbe56bdfa42489415eb6696638c2bb336 (commit)
       via  dcc13bac8192a1a1075f5596d0b014441ae5b33a (commit)
       via  3a9f6442080843c97737aae74608cb2d188be1da (commit)
       via  a167017da0181a84d0c5493364bc0f7f3fa3e942 (commit)
       via  f0f6c4a0d9d29b0e9651b79096f264093c298c9a (commit)
       via  fe0f3f07a4dea0506844d47529752484cf0347a7 (commit)
       via  65473dca3509b72453f430c5ce6e69fe97961d08 (commit)
       via  28cbbd7ed4a4d051e1b920f5c397fd121123af5e (commit)
       via  5786e88839dd7ef52885a03aedfc7642b19cd5a1 (commit)
       via  edb0ce16ee7ccef6cec38c9787ba0dadde5da697 (commit)
       via  8b5ee21b5b8e1dca0613151e4b38df55381d0e6f (commit)
       via  aba0f702843b8b385b8fe0c5d7b19f9cbbc433ac (commit)
       via  37504d34196d446ae8fa2e01fa84bdc0ecbbed78 (commit)
       via  5e8d8fd1172273a14c9306f36eefc7aef8442dc6 (commit)
       via  c65d58daa5d570be4c19fc207abeda7e01ae1466 (commit)
       via  b49ca531d77614d3029b4d34c39df3e2938c39cf (commit)
       via  cfe49be676877567de9704e8d7968d611fa77fc9 (commit)
       via  e3c678438f8400d15843dc745df83525a53b07e3 (commit)
       via  c9cbcb68f87f432452991e5e904fff7d65395e6c (commit)
       via  7f8d3a8c260418c02c85e74d9da27c45802be837 (commit)
       via  70593b8d415d8858123a3df4eb148ce686611c26 (commit)
       via  f3d184828b69929818603bd1e88ef8cb98a472df (commit)
       via  7d3e2f9561e8cebfa7c3d4fddd8d2c586aadccd8 (commit)
       via  5b7e7988f1992e04e676c0c1e3e969ea2ef3b627 (commit)
       via  8b7a3f52ea0b0ee7ae7a744e76b337e90d4da1f6 (commit)
       via  ef8180fa2f0b90b1656b1d8c7ef12bc6ea028a0a (commit)
       via  7f0ee8806944ff39e3545572ebad8f63b8e0938d (commit)
       via  e11a6fa13214f91ffc602e53574736174ca6e8e9 (commit)
       via  62f757227d45f1bbd338a0e27b22b8deb69f68b2 (commit)
       via  207a57dad1fad3048964da01aab0baa4d0c81f93 (commit)
       via  228200078ce8c5abd6bd2070fe89bdb6fbaa5d05 (commit)
       via  32d7c9cf30412f9a79cacf87e938784cfdff0fa5 (commit)
       via  68f7eb6fee941631e6999f729b98e6050a13a3d2 (commit)
       via  dbc43c1a85ddf08bb2eab90b6e7cadabdc8e357c (commit)
       via  1c3da825ed0a2c7ca95eb4bf91aebfe56109dd33 (commit)
       via  ff3197c85e5520569eb8d67edaa9b7e9bc2ba427 (commit)
       via  b3b7bc4a913960c0dd1cba50eb690534b15b37b4 (commit)
       via  bb5bafcc65c3d78fd469b3c5041846ddbc67870a (commit)
       via  6319203009ed914504e23ff263b090e6250709fa (commit)
       via  be33bc8c17b2e12b96d176d16e60f8ebe3fe1c06 (commit)
       via  877cfadcbed1ebae8c94c8867bac2c25086d7ed4 (commit)
       via  c4c1bd525a660118ecf3b53811cd112a992fdc81 (commit)
       via  3e18e35172808479d02e02985563d658fec7bb0f (commit)
       via  4dd738fa98e3726fcd99a37e334b5ea8db4e5ee0 (commit)
       via  8c553b96204b8f1cdb43d27cd02814cf4d45a1dc (commit)
       via  bdf65674185522fd6202840513574d8e216e6fc5 (commit)
       via  7b8448155a3b9ca4c3c17c4879ed7ca256b4a42a (commit)
       via  5d0a9458a1910b76c432c83ef4dd9a3337fdff38 (commit)
       via  4710b7919dcdc3435bab7bd7e706169175991cf5 (commit)
       via  f526bdc34a4815fd0e0978b879c6fca70c0887c8 (commit)
       via  0de2dbbdaa1c0906e105cfc685affdb3d03dc9e7 (commit)
       via  39bdc716190a21ee7c70d9450fc121a0513ff8ba (commit)
       via  54c77552a4ff0403eb627408ed38a9d8b60606b3 (commit)
       via  8822913c680fa37634b9ab103bc962b77ebde830 (commit)
       via  0a61c946e683e8a44db3212656aa4b3da82ffd8e (commit)
       via  bc0041ffcbc42f92edc6bde05c8ba3ce392873fc (commit)
       via  80b9537fc9d1ae6f8c907efdf01be0505c693eb8 (commit)
       via  b6c145e81728b3916370000a83648280bbfb94d2 (commit)
       via  c6b3db3a8e2fcb7fe8d44cdd60e12fe7e2be571f (commit)
      from  ff8bab30fa2c0dabc4ef54d538b6979adf813beb (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.


commit 58afb8dfcfa3fefa8e1b28dc418b97c51a73b7dc
Author: Lisa Knox <lisaknox83 at gmail.com>
Date:   Tue Oct 10 15:31:09 2023 -0400

    15768: merge cleanup Arvados-DCO-1.1-Signed-off-by: Lisa Knox <lisa.knox at curii.com>

diff --git a/src/components/icon/icon.tsx b/src/components/icon/icon.tsx
index 20b87b20..5f3f4e65 100644
--- a/src/components/icon/icon.tsx
+++ b/src/components/icon/icon.tsx
@@ -2,218 +2,261 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import React from 'react';
-import { Badge, SvgIcon, Tooltip } from '@material-ui/core';
-import Add from '@material-ui/icons/Add';
-import ArrowBack from '@material-ui/icons/ArrowBack';
-import ArrowDropDown from '@material-ui/icons/ArrowDropDown';
-import Build from '@material-ui/icons/Build';
-import Cached from '@material-ui/icons/Cached';
-import DescriptionIcon from '@material-ui/icons/Description';
-import ChevronLeft from '@material-ui/icons/ChevronLeft';
-import CloudUpload from '@material-ui/icons/CloudUpload';
-import Code from '@material-ui/icons/Code';
-import Create from '@material-ui/icons/Create';
-import ImportContacts from '@material-ui/icons/ImportContacts';
-import ChevronRight from '@material-ui/icons/ChevronRight';
-import Close from '@material-ui/icons/Close';
-import ContentCopy from '@material-ui/icons/FileCopyOutlined';
-import CreateNewFolder from '@material-ui/icons/CreateNewFolder';
-import Delete from '@material-ui/icons/Delete';
-import DeviceHub from '@material-ui/icons/DeviceHub';
-import Edit from '@material-ui/icons/Edit';
-import ErrorRoundedIcon from '@material-ui/icons/ErrorRounded';
-import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
-import FlipToFront from '@material-ui/icons/FlipToFront';
-import Folder from '@material-ui/icons/Folder';
-import FolderShared from '@material-ui/icons/FolderShared';
-import Pageview from '@material-ui/icons/Pageview';
-import GetApp from '@material-ui/icons/GetApp';
-import Help from '@material-ui/icons/Help';
-import HelpOutline from '@material-ui/icons/HelpOutline';
-import History from '@material-ui/icons/History';
-import Inbox from '@material-ui/icons/Inbox';
-import Memory from '@material-ui/icons/Memory';
-import MoveToInbox from '@material-ui/icons/MoveToInbox';
-import Info from '@material-ui/icons/Info';
-import Input from '@material-ui/icons/Input';
-import InsertDriveFile from '@material-ui/icons/InsertDriveFile';
-import LastPage from '@material-ui/icons/LastPage';
-import LibraryBooks from '@material-ui/icons/LibraryBooks';
-import ListAlt from '@material-ui/icons/ListAlt';
-import Menu from '@material-ui/icons/Menu';
-import MoreVert from '@material-ui/icons/MoreVert';
-import Mail from '@material-ui/icons/Mail';
-import Notifications from '@material-ui/icons/Notifications';
-import OpenInNew from '@material-ui/icons/OpenInNew';
-import People from '@material-ui/icons/People';
-import Person from '@material-ui/icons/Person';
-import PersonAdd from '@material-ui/icons/PersonAdd';
-import PlayArrow from '@material-ui/icons/PlayArrow';
-import Public from '@material-ui/icons/Public';
-import RateReview from '@material-ui/icons/RateReview';
-import RestoreFromTrash from '@material-ui/icons/History';
-import Search from '@material-ui/icons/Search';
-import SettingsApplications from '@material-ui/icons/SettingsApplications';
-import SettingsEthernet from '@material-ui/icons/SettingsEthernet';
-import Settings from '@material-ui/icons/Settings';
-import Star from '@material-ui/icons/Star';
-import StarBorder from '@material-ui/icons/StarBorder';
-import Warning from '@material-ui/icons/Warning';
-import VpnKey from '@material-ui/icons/VpnKey';
-import LinkOutlined from '@material-ui/icons/LinkOutlined';
-import RemoveRedEye from '@material-ui/icons/RemoveRedEye';
-import Computer from '@material-ui/icons/Computer';
-import WrapText from '@material-ui/icons/WrapText';
-import TextIncrease from '@material-ui/icons/ZoomIn';
-import TextDecrease from '@material-ui/icons/ZoomOut';
-import FullscreenSharp from '@material-ui/icons/FullscreenSharp';
-import FullscreenExitSharp from '@material-ui/icons/FullscreenExitSharp';
-import ExitToApp from '@material-ui/icons/ExitToApp';
-import CheckCircleOutline from '@material-ui/icons/CheckCircleOutline';
-import RemoveCircleOutline from '@material-ui/icons/RemoveCircleOutline';
-import NotInterested from '@material-ui/icons/NotInterested';
-import Image from '@material-ui/icons/Image';
-import Stop from '@material-ui/icons/Stop';
+import React from "react";
+import { Badge, SvgIcon, Tooltip } from "@material-ui/core";
+import Add from "@material-ui/icons/Add";
+import ArrowBack from "@material-ui/icons/ArrowBack";
+import ArrowDropDown from "@material-ui/icons/ArrowDropDown";
+import Build from "@material-ui/icons/Build";
+import Cached from "@material-ui/icons/Cached";
+import DescriptionIcon from "@material-ui/icons/Description";
+import ChevronLeft from "@material-ui/icons/ChevronLeft";
+import CloudUpload from "@material-ui/icons/CloudUpload";
+import Code from "@material-ui/icons/Code";
+import Create from "@material-ui/icons/Create";
+import ImportContacts from "@material-ui/icons/ImportContacts";
+import ChevronRight from "@material-ui/icons/ChevronRight";
+import Close from "@material-ui/icons/Close";
+import ContentCopy from "@material-ui/icons/FileCopyOutlined";
+import CreateNewFolder from "@material-ui/icons/CreateNewFolder";
+import Delete from "@material-ui/icons/Delete";
+import DeviceHub from "@material-ui/icons/DeviceHub";
+import Edit from "@material-ui/icons/Edit";
+import ErrorRoundedIcon from "@material-ui/icons/ErrorRounded";
+import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
+import FlipToFront from "@material-ui/icons/FlipToFront";
+import Folder from "@material-ui/icons/Folder";
+import FolderShared from "@material-ui/icons/FolderShared";
+import Pageview from "@material-ui/icons/Pageview";
+import GetApp from "@material-ui/icons/GetApp";
+import Help from "@material-ui/icons/Help";
+import HelpOutline from "@material-ui/icons/HelpOutline";
+import History from "@material-ui/icons/History";
+import Inbox from "@material-ui/icons/Inbox";
+import Memory from "@material-ui/icons/Memory";
+import MoveToInbox from "@material-ui/icons/MoveToInbox";
+import Info from "@material-ui/icons/Info";
+import Input from "@material-ui/icons/Input";
+import InsertDriveFile from "@material-ui/icons/InsertDriveFile";
+import LastPage from "@material-ui/icons/LastPage";
+import LibraryBooks from "@material-ui/icons/LibraryBooks";
+import ListAlt from "@material-ui/icons/ListAlt";
+import Menu from "@material-ui/icons/Menu";
+import MoreVert from "@material-ui/icons/MoreVert";
+import MoreHoriz from "@material-ui/icons/MoreHoriz";
+import Mail from "@material-ui/icons/Mail";
+import Notifications from "@material-ui/icons/Notifications";
+import OpenInNew from "@material-ui/icons/OpenInNew";
+import People from "@material-ui/icons/People";
+import Person from "@material-ui/icons/Person";
+import PersonAdd from "@material-ui/icons/PersonAdd";
+import PlayArrow from "@material-ui/icons/PlayArrow";
+import Public from "@material-ui/icons/Public";
+import RateReview from "@material-ui/icons/RateReview";
+import RestoreFromTrash from "@material-ui/icons/History";
+import Search from "@material-ui/icons/Search";
+import SettingsApplications from "@material-ui/icons/SettingsApplications";
+import SettingsEthernet from "@material-ui/icons/SettingsEthernet";
+import Settings from "@material-ui/icons/Settings";
+import Star from "@material-ui/icons/Star";
+import StarBorder from "@material-ui/icons/StarBorder";
+import Warning from "@material-ui/icons/Warning";
+import VpnKey from "@material-ui/icons/VpnKey";
+import LinkOutlined from "@material-ui/icons/LinkOutlined";
+import RemoveRedEye from "@material-ui/icons/RemoveRedEye";
+import Computer from "@material-ui/icons/Computer";
+import WrapText from "@material-ui/icons/WrapText";
+import TextIncrease from "@material-ui/icons/ZoomIn";
+import TextDecrease from "@material-ui/icons/ZoomOut";
+import FullscreenSharp from "@material-ui/icons/FullscreenSharp";
+import FullscreenExitSharp from "@material-ui/icons/FullscreenExitSharp";
+import ExitToApp from "@material-ui/icons/ExitToApp";
+import CheckCircleOutline from "@material-ui/icons/CheckCircleOutline";
+import RemoveCircleOutline from "@material-ui/icons/RemoveCircleOutline";
+import NotInterested from "@material-ui/icons/NotInterested";
+import Image from "@material-ui/icons/Image";
+import Stop from "@material-ui/icons/Stop";
+import FileCopy from "@material-ui/icons/FileCopy";
 
 // Import FontAwesome icons
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faPencilAlt, faSlash, faUsers, faEllipsisH } from '@fortawesome/free-solid-svg-icons';
-import { FormatAlignLeft } from '@material-ui/icons';
-library.add(
-    faPencilAlt,
-    faSlash,
-    faUsers,
-    faEllipsisH,
-);
+import { library } from "@fortawesome/fontawesome-svg-core";
+import { faPencilAlt, faSlash, faUsers, faEllipsisH } from "@fortawesome/free-solid-svg-icons";
+import { FormatAlignLeft } from "@material-ui/icons";
+library.add(faPencilAlt, faSlash, faUsers, faEllipsisH);
 
-export const FreezeIcon = (props: any) =>
+export const FreezeIcon = (props: any) => (
     <SvgIcon {...props}>
         <path d="M20.79,13.95L18.46,14.57L16.46,13.44V10.56L18.46,9.43L20.79,10.05L21.31,8.12L19.54,7.65L20,5.88L18.07,5.36L17.45,7.69L15.45,8.82L13,7.38V5.12L14.71,3.41L13.29,2L12,3.29L10.71,2L9.29,3.41L11,5.12V7.38L8.5,8.82L6.5,7.69L5.92,5.36L4,5.88L4.47,7.65L2.7,8.12L3.22,10.05L5.55,9.43L7.55,10.56V13.45L5.55,14.58L3.22,13.96L2.7,15.89L4.47,16.36L4,18.12L5.93,18.64L6.55,16.31L8.55,15.18L11,16.62V18.88L9.29,20.59L10.71,22L12,20.71L13.29,22L14.7,20.59L13,18.88V16.62L15.5,15.17L17.5,16.3L18.12,18.63L20,18.12L19.53,16.35L21.3,15.88L20.79,13.95M9.5,10.56L12,9.11L14.5,10.56V13.44L12,14.89L9.5,13.44V10.56Z" />
     </SvgIcon>
+);
 
-export const UnfreezeIcon = (props: any) =>
+export const UnfreezeIcon = (props: any) => (
     <SvgIcon {...props}>
         <path d="M11 5.12L9.29 3.41L10.71 2L12 3.29L13.29 2L14.71 3.41L13 5.12V7.38L15.45 8.82L17.45 7.69L18.07 5.36L20 5.88L19.54 7.65L21.31 8.12L20.79 10.05L18.46 9.43L16.46 10.56V13.26L14.5 11.3V10.56L12.74 9.54L10.73 7.53L11 7.38V5.12M18.46 14.57L16.87 13.67L19.55 16.35L21.3 15.88L20.79 13.95L18.46 14.57M13 16.62V18.88L14.7 20.59L13.29 22L12 20.71L10.71 22L9.29 20.59L11 18.88V16.62L8.55 15.18L6.55 16.31L5.93 18.64L4 18.12L4.47 16.36L2.7 15.89L3.22 13.96L5.55 14.58L7.55 13.45V10.56L5.55 9.43L3.22 10.05L2.7 8.12L4.47 7.65L4 5.89L1.11 3L2.39 1.73L22.11 21.46L20.84 22.73L14.1 16L13 16.62M12 14.89L12.63 14.5L9.5 11.39V13.44L12 14.89Z" />
     </SvgIcon>
+);
 
-export const PendingIcon = (props: any) =>
+export const PendingIcon = (props: any) => (
     <span {...props}>
-        <span className='fas fa-ellipsis-h' />
+        <span className="fas fa-ellipsis-h" />
     </span>
+);
 
-export const ReadOnlyIcon = (props: any) =>
+export const ReadOnlyIcon = (props: any) => (
     <span {...props}>
         <div className="fa-layers fa-1x fa-fw">
-            <span className="fas fa-slash"
-                data-fa-mask="fas fa-pencil-alt" data-fa-transform="down-1.5" />
+            <span
+                className="fas fa-slash"
+                data-fa-mask="fas fa-pencil-alt"
+                data-fa-transform="down-1.5"
+            />
             <span className="fas fa-slash" />
         </div>
-    </span>;
+    </span>
+);
 
-export const GroupsIcon = (props: any) =>
+export const GroupsIcon = (props: any) => (
     <span {...props}>
         <span className="fas fa-users" />
-    </span>;
+    </span>
+);
 
-export const CollectionOldVersionIcon = (props: any) =>
-    <Tooltip title='Old version'>
-        <Badge badgeContent={<History fontSize='small' />}>
+export const CollectionOldVersionIcon = (props: any) => (
+    <Tooltip title="Old version">
+        <Badge badgeContent={<History fontSize="small" />}>
             <CollectionIcon {...props} />
         </Badge>
-    </Tooltip>;
+    </Tooltip>
+);
 
 // https://materialdesignicons.com/icon/image-off
-export const ImageOffIcon = (props: any) =>
+export const ImageOffIcon = (props: any) => (
     <SvgIcon {...props}>
         <path d="M21 17.2L6.8 3H19C20.1 3 21 3.9 21 5V17.2M20.7 22L19.7 21H5C3.9 21 3 20.1 3 19V4.3L2 3.3L3.3 2L22 20.7L20.7 22M16.8 18L12.9 14.1L11 16.5L8.5 13.5L5 18H16.8Z" />
-    </SvgIcon>;
+    </SvgIcon>
+);
 
 // https://materialdesignicons.com/icon/inbox-arrow-up
-export const OutputIcon: IconType = (props: any) =>
+export const OutputIcon: IconType = (props: any) => (
     <SvgIcon {...props}>
         <path d="M14,14H10V11H8L12,7L16,11H14V14M16,11M5,15V5H19V15H15A3,3 0 0,1 12,18A3,3 0 0,1 9,15H5M19,3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3" />
-    </SvgIcon>;
+    </SvgIcon>
+);
 
-export type IconType = React.SFC<{ className?: string, style?: object }>;
+// https://pictogrammers.com/library/mdi/icon/file-move/
+export const FileMoveIcon: IconType = (props: any) => (
+    <SvgIcon {...props}>
+        <path d="M14,17H18V14L23,18.5L18,23V20H14V17M13,9H18.5L13,3.5V9M6,2H14L20,8V12.34C19.37,12.12 18.7,12 18,12A6,6 0 0,0 12,18C12,19.54 12.58,20.94 13.53,22H6C4.89,22 4,21.1 4,20V4A2,2 0 0,1 6,2Z" />
+    </SvgIcon>
+);
 
-export const AddIcon: IconType = (props) => <Add {...props} />;
-export const AddFavoriteIcon: IconType = (props) => <StarBorder {...props} />;
-export const AdminMenuIcon: IconType = (props) => <Build {...props} />;
-export const AdvancedIcon: IconType = (props) => <SettingsApplications {...props} />;
-export const AttributesIcon: IconType = (props) => <ListAlt {...props} />;
-export const BackIcon: IconType = (props) => <ArrowBack {...props} />;
-export const CustomizeTableIcon: IconType = (props) => <Menu {...props} />;
-export const CommandIcon: IconType = (props) => <LastPage {...props} />;
-export const CopyIcon: IconType = (props) => <ContentCopy {...props} />;
-export const CollectionIcon: IconType = (props) => <LibraryBooks {...props} />;
-export const CloseIcon: IconType = (props) => <Close {...props} />;
-export const CloudUploadIcon: IconType = (props) => <CloudUpload {...props} />;
-export const DefaultIcon: IconType = (props) => <RateReview {...props} />;
-export const DetailsIcon: IconType = (props) => <Info {...props} />;
-export const DirectoryIcon: IconType = (props) => <Folder {...props} />;
-export const DownloadIcon: IconType = (props) => <GetApp {...props} />;
-export const EditSavedQueryIcon: IconType = (props) => <Create {...props} />;
-export const ExpandIcon: IconType = (props) => <ExpandMoreIcon {...props} />;
-export const ErrorIcon: IconType = (props) => <ErrorRoundedIcon style={{ color: '#ff0000' }} {...props} />;
-export const FavoriteIcon: IconType = (props) => <Star {...props} />;
-export const FileIcon: IconType = (props) => <DescriptionIcon {...props} />;
-export const HelpIcon: IconType = (props) => <Help {...props} />;
-export const HelpOutlineIcon: IconType = (props) => <HelpOutline {...props} />;
-export const ImportContactsIcon: IconType = (props) => <ImportContacts {...props} />;
-export const InfoIcon: IconType = (props) => <Info {...props} />;
-export const FileInputIcon: IconType = (props) => <InsertDriveFile {...props} />;
-export const KeyIcon: IconType = (props) => <VpnKey {...props} />;
-export const LogIcon: IconType = (props) => <SettingsEthernet {...props} />;
-export const MailIcon: IconType = (props) => <Mail {...props} />;
-export const MaximizeIcon: IconType = (props) => <FullscreenSharp {...props} />;
-export const MemoryIcon: IconType = (props) => <Memory {...props} />;
-export const UnMaximizeIcon: IconType = (props) => <FullscreenExitSharp {...props} />;
-export const MoreOptionsIcon: IconType = (props) => <MoreVert {...props} />;
-export const MoveToIcon: IconType = (props) => <Input {...props} />;
-export const NewProjectIcon: IconType = (props) => <CreateNewFolder {...props} />;
-export const NotificationIcon: IconType = (props) => <Notifications {...props} />;
-export const OpenIcon: IconType = (props) => <OpenInNew {...props} />;
-export const InputIcon: IconType = (props) => <MoveToInbox {...props} />;
-export const PaginationDownIcon: IconType = (props) => <ArrowDropDown {...props} />;
-export const PaginationLeftArrowIcon: IconType = (props) => <ChevronLeft {...props} />;
-export const PaginationRightArrowIcon: IconType = (props) => <ChevronRight {...props} />;
-export const ProcessIcon: IconType = (props) => <Settings {...props} />;
-export const ProjectIcon: IconType = (props) => <Folder {...props} />;
-export const FilterGroupIcon: IconType = (props) => <Pageview {...props} />;
-export const ProjectsIcon: IconType = (props) => <Inbox {...props} />;
-export const ProvenanceGraphIcon: IconType = (props) => <DeviceHub {...props} />;
-export const RemoveIcon: IconType = (props) => <Delete {...props} />;
-export const RemoveFavoriteIcon: IconType = (props) => <Star {...props} />;
-export const PublicFavoriteIcon: IconType = (props) => <Public {...props} />;
-export const RenameIcon: IconType = (props) => <Edit {...props} />;
-export const RestoreVersionIcon: IconType = (props) => <FlipToFront {...props} />;
-export const RestoreFromTrashIcon: IconType = (props) => <RestoreFromTrash {...props} />;
-export const ReRunProcessIcon: IconType = (props) => <Cached {...props} />;
-export const SearchIcon: IconType = (props) => <Search {...props} />;
-export const ShareIcon: IconType = (props) => <PersonAdd {...props} />;
-export const ShareMeIcon: IconType = (props) => <People {...props} />;
-export const SidePanelRightArrowIcon: IconType = (props) => <PlayArrow {...props} />;
-export const TrashIcon: IconType = (props) => <Delete {...props} />;
-export const UserPanelIcon: IconType = (props) => <Person {...props} />;
-export const UsedByIcon: IconType = (props) => <Folder {...props} />;
-export const WorkflowIcon: IconType = (props) => <Code {...props} />;
-export const WarningIcon: IconType = (props) => <Warning style={{ color: '#fbc02d', height: '30px', width: '30px' }} {...props} />;
-export const Link: IconType = (props) => <LinkOutlined {...props} />;
-export const FolderSharedIcon: IconType = (props) => <FolderShared {...props} />;
-export const CanReadIcon: IconType = (props) => <RemoveRedEye {...props} />;
-export const CanWriteIcon: IconType = (props) => <Edit {...props} />;
-export const CanManageIcon: IconType = (props) => <Computer {...props} />;
-export const AddUserIcon: IconType = (props) => <PersonAdd {...props} />;
-export const WordWrapOnIcon: IconType = (props) => <WrapText {...props} />;
-export const WordWrapOffIcon: IconType = (props) => <FormatAlignLeft {...props} />;
-export const TextIncreaseIcon: IconType = (props) => <TextIncrease {...props} />;
-export const TextDecreaseIcon: IconType = (props) => <TextDecrease {...props} />;
-export const DeactivateUserIcon: IconType = (props) => <NotInterested {...props} />;
-export const LoginAsIcon: IconType = (props) => <ExitToApp {...props} />;
-export const ActiveIcon: IconType = (props) => <CheckCircleOutline {...props} />;
-export const SetupIcon: IconType = (props) => <RemoveCircleOutline {...props} />;
-export const InactiveIcon: IconType = (props) => <NotInterested {...props} />;
-export const ImageIcon: IconType = (props) => <Image {...props} />;
-export const StartIcon: IconType = (props) => <PlayArrow {...props} />;
-export const StopIcon: IconType = (props) => <Stop {...props} />;
+// https://pictogrammers.com/library/mdi/icon/checkbox-multiple-outline/
+export const CheckboxMultipleOutline: IconType = (props: any) => (
+    <SvgIcon {...props}>
+        <path d="M20,2H8A2,2 0 0,0 6,4V16A2,2 0 0,0 8,18H20A2,2 0 0,0 22,16V4A2,2 0 0,0 20,2M20,16H8V4H20V16M16,20V22H4A2,2 0 0,1 2,20V7H4V20H16M18.53,8.06L17.47,7L12.59,11.88L10.47,9.76L9.41,10.82L12.59,14L18.53,8.06Z" />
+    </SvgIcon>
+);
+
+// https://pictogrammers.com/library/mdi/icon/checkbox-multiple-blank-outline/
+export const CheckboxMultipleBlankOutline: IconType = (props: any) => (
+    <SvgIcon {...props}>
+        <path d="M20,16V4H8V16H20M22,16A2,2 0 0,1 20,18H8C6.89,18 6,17.1 6,16V4C6,2.89 6.89,2 8,2H20A2,2 0 0,1 22,4V16M16,20V22H4A2,2 0 0,1 2,20V7H4V20H16Z" />
+    </SvgIcon>
+);
+
+export type IconType = React.SFC<{ className?: string; style?: object }>;
+
+export const AddIcon: IconType = props => <Add {...props} />;
+export const AddFavoriteIcon: IconType = props => <StarBorder {...props} />;
+export const AdminMenuIcon: IconType = props => <Build {...props} />;
+export const AdvancedIcon: IconType = props => <SettingsApplications {...props} />;
+export const AttributesIcon: IconType = props => <ListAlt {...props} />;
+export const BackIcon: IconType = props => <ArrowBack {...props} />;
+export const CustomizeTableIcon: IconType = props => <Menu {...props} />;
+export const CommandIcon: IconType = props => <LastPage {...props} />;
+export const CopyIcon: IconType = props => <ContentCopy {...props} />;
+export const FileCopyIcon: IconType = props => <FileCopy {...props} />;
+export const CollectionIcon: IconType = props => <LibraryBooks {...props} />;
+export const CloseIcon: IconType = props => <Close {...props} />;
+export const CloudUploadIcon: IconType = props => <CloudUpload {...props} />;
+export const DefaultIcon: IconType = props => <RateReview {...props} />;
+export const DetailsIcon: IconType = props => <Info {...props} />;
+export const DirectoryIcon: IconType = props => <Folder {...props} />;
+export const DownloadIcon: IconType = props => <GetApp {...props} />;
+export const EditSavedQueryIcon: IconType = props => <Create {...props} />;
+export const ExpandIcon: IconType = props => <ExpandMoreIcon {...props} />;
+export const ErrorIcon: IconType = props => (
+    <ErrorRoundedIcon
+        style={{ color: "#ff0000" }}
+        {...props}
+    />
+);
+export const FavoriteIcon: IconType = props => <Star {...props} />;
+export const FileIcon: IconType = props => <DescriptionIcon {...props} />;
+export const HelpIcon: IconType = props => <Help {...props} />;
+export const HelpOutlineIcon: IconType = props => <HelpOutline {...props} />;
+export const ImportContactsIcon: IconType = props => <ImportContacts {...props} />;
+export const InfoIcon: IconType = props => <Info {...props} />;
+export const FileInputIcon: IconType = props => <InsertDriveFile {...props} />;
+export const KeyIcon: IconType = props => <VpnKey {...props} />;
+export const LogIcon: IconType = props => <SettingsEthernet {...props} />;
+export const MailIcon: IconType = props => <Mail {...props} />;
+export const MaximizeIcon: IconType = props => <FullscreenSharp {...props} />;
+export const MemoryIcon: IconType = props => <Memory {...props} />;
+export const UnMaximizeIcon: IconType = props => <FullscreenExitSharp {...props} />;
+export const MoreVerticalIcon: IconType = props => <MoreVert {...props} />;
+export const MoreHorizontalIcon: IconType = props => <MoreHoriz {...props} />;
+export const MoveToIcon: IconType = props => <Input {...props} />;
+export const NewProjectIcon: IconType = props => <CreateNewFolder {...props} />;
+export const NotificationIcon: IconType = props => <Notifications {...props} />;
+export const OpenIcon: IconType = props => <OpenInNew {...props} />;
+export const InputIcon: IconType = props => <MoveToInbox {...props} />;
+export const PaginationDownIcon: IconType = props => <ArrowDropDown {...props} />;
+export const PaginationLeftArrowIcon: IconType = props => <ChevronLeft {...props} />;
+export const PaginationRightArrowIcon: IconType = props => <ChevronRight {...props} />;
+export const ProcessIcon: IconType = props => <Settings {...props} />;
+export const ProjectIcon: IconType = props => <Folder {...props} />;
+export const FilterGroupIcon: IconType = props => <Pageview {...props} />;
+export const ProjectsIcon: IconType = props => <Inbox {...props} />;
+export const ProvenanceGraphIcon: IconType = props => <DeviceHub {...props} />;
+export const RemoveIcon: IconType = props => <Delete {...props} />;
+export const RemoveFavoriteIcon: IconType = props => <Star {...props} />;
+export const PublicFavoriteIcon: IconType = props => <Public {...props} />;
+export const RenameIcon: IconType = props => <Edit {...props} />;
+export const RestoreVersionIcon: IconType = props => <FlipToFront {...props} />;
+export const RestoreFromTrashIcon: IconType = props => <RestoreFromTrash {...props} />;
+export const ReRunProcessIcon: IconType = props => <Cached {...props} />;
+export const SearchIcon: IconType = props => <Search {...props} />;
+export const ShareIcon: IconType = props => <PersonAdd {...props} />;
+export const ShareMeIcon: IconType = props => <People {...props} />;
+export const SidePanelRightArrowIcon: IconType = props => <PlayArrow {...props} />;
+export const TrashIcon: IconType = props => <Delete {...props} />;
+export const UserPanelIcon: IconType = props => <Person {...props} />;
+export const UsedByIcon: IconType = props => <Folder {...props} />;
+export const WorkflowIcon: IconType = props => <Code {...props} />;
+export const WarningIcon: IconType = props => (
+    <Warning
+        style={{ color: "#fbc02d", height: "30px", width: "30px" }}
+        {...props}
+    />
+);
+export const Link: IconType = props => <LinkOutlined {...props} />;
+export const FolderSharedIcon: IconType = props => <FolderShared {...props} />;
+export const CanReadIcon: IconType = props => <RemoveRedEye {...props} />;
+export const CanWriteIcon: IconType = props => <Edit {...props} />;
+export const CanManageIcon: IconType = props => <Computer {...props} />;
+export const AddUserIcon: IconType = props => <PersonAdd {...props} />;
+export const WordWrapOnIcon: IconType = props => <WrapText {...props} />;
+export const WordWrapOffIcon: IconType = props => <FormatAlignLeft {...props} />;
+export const TextIncreaseIcon: IconType = props => <TextIncrease {...props} />;
+export const TextDecreaseIcon: IconType = props => <TextDecrease {...props} />;
+export const DeactivateUserIcon: IconType = props => <NotInterested {...props} />;
+export const LoginAsIcon: IconType = props => <ExitToApp {...props} />;
+export const ActiveIcon: IconType = props => <CheckCircleOutline {...props} />;
+export const SetupIcon: IconType = props => <RemoveCircleOutline {...props} />;
+export const InactiveIcon: IconType = props => <NotInterested {...props} />;
+export const ImageIcon: IconType = props => <Image {...props} />;
+export const StartIcon: IconType = props => <PlayArrow {...props} />;
+export const StopIcon: IconType = props => <Stop {...props} />;
+export const SelectAllIcon: IconType = props => <CheckboxMultipleOutline {...props} />;
+export const SelectNoneIcon: IconType = props => <CheckboxMultipleBlankOutline {...props} />;
diff --git a/src/store/store.ts b/src/store/store.ts
index e0af6047..ac56d407 100644
--- a/src/store/store.ts
+++ b/src/store/store.ts
@@ -78,6 +78,8 @@ import { tooltipsMiddleware } from "./tooltips/tooltips-middleware";
 import { sidePanelReducer } from "./side-panel/side-panel-reducer";
 import { bannerReducer } from "./banner/banner-reducer";
 import { multiselectReducer } from "./multiselect/multiselect-reducer";
+import { composeWithDevTools } from "redux-devtools-extension";
+const composeEnhancers = (process.env.NODE_ENV === "development" && window && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose;
 
 declare global {
     interface Window {
@@ -85,9 +87,6 @@ declare global {
     }
 }
 
-const composeEnhancers = (process.env.NODE_ENV === "development" && window && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose;
-import { composeWithDevTools } from "redux-devtools-extension";
-
 export type RootState = ReturnType<ReturnType<typeof createRootReducer>>;
 
 export type RootStore = Store<RootState, Action> & { dispatch: Dispatch<any> };
diff --git a/src/views-components/data-explorer/renderers.tsx b/src/views-components/data-explorer/renderers.tsx
index 5accd052..dca06084 100644
--- a/src/views-components/data-explorer/renderers.tsx
+++ b/src/views-components/data-explorer/renderers.tsx
@@ -2,17 +2,10 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-<<<<<<< HEAD
-import React from 'react';
-import { Grid, Typography, withStyles, Tooltip, IconButton, Checkbox, Chip } from '@material-ui/core';
-import { FavoriteStar, PublicFavoriteStar } from '../favorite-star/favorite-star';
-import { Resource, ResourceKind, TrashableResource } from 'models/resource';
-=======
 import React from "react";
 import { Grid, Typography, withStyles, Tooltip, IconButton, Checkbox, Chip } from "@material-ui/core";
 import { FavoriteStar, PublicFavoriteStar } from "../favorite-star/favorite-star";
 import { Resource, ResourceKind, TrashableResource } from "models/resource";
->>>>>>> main
 import {
     FreezeIcon,
     ProjectIcon,
@@ -28,49 +21,6 @@ import {
     ActiveIcon,
     SetupIcon,
     InactiveIcon,
-<<<<<<< HEAD
-} from 'components/icon/icon';
-import { formatDate, formatFileSize, formatTime } from 'common/formatters';
-import { resourceLabel } from 'common/labels';
-import { connect, DispatchProp } from 'react-redux';
-import { RootState } from 'store/store';
-import { getResource, filterResources } from 'store/resources/resources';
-import { GroupContentsResource } from 'services/groups-service/groups-service';
-import { getProcess, Process, getProcessStatus, getProcessStatusStyles, getProcessRuntime } from 'store/processes/process';
-import { ArvadosTheme } from 'common/custom-theme';
-import { compose, Dispatch } from 'redux';
-import { WorkflowResource } from 'models/workflow';
-import { ResourceStatus as WorkflowStatus } from 'views/workflow-panel/workflow-panel-view';
-import { getUuidPrefix, openRunProcess } from 'store/workflow-panel/workflow-panel-actions';
-import { openSharingDialog } from 'store/sharing-dialog/sharing-dialog-actions';
-import { getUserFullname, getUserDisplayName, User, UserResource } from 'models/user';
-import { toggleIsAdmin } from 'store/users/users-actions';
-import { LinkClass, LinkResource } from 'models/link';
-import { navigateTo, navigateToGroupDetails, navigateToUserProfile } from 'store/navigation/navigation-action';
-import { withResourceData } from 'views-components/data-explorer/with-resources';
-import { CollectionResource } from 'models/collection';
-import { IllegalNamingWarning } from 'components/warning/warning';
-import { loadResource } from 'store/resources/resources-actions';
-import { BuiltinGroups, getBuiltinGroupUuid, GroupClass, GroupResource, isBuiltinGroup } from 'models/group';
-import { openRemoveGroupMemberDialog } from 'store/group-details-panel/group-details-panel-actions';
-import { setMemberIsHidden } from 'store/group-details-panel/group-details-panel-actions';
-import { formatPermissionLevel } from 'views-components/sharing-dialog/permission-select';
-import { PermissionLevel } from 'models/permission';
-import { openPermissionEditContextMenu } from 'store/context-menu/context-menu-actions';
-import { getUserUuid } from 'common/getuser';
-import { VirtualMachinesResource } from 'models/virtual-machines';
-import { CopyToClipboardSnackbar } from 'components/copy-to-clipboard-snackbar/copy-to-clipboard-snackbar';
-import { ProjectResource } from 'models/project';
-import { ProcessResource } from 'models/process';
-
-const renderName = (dispatch: Dispatch, item: GroupContentsResource) => {
-    const navFunc = 'groupClass' in item && item.groupClass === GroupClass.ROLE ? navigateToGroupDetails : navigateTo;
-    return (
-        <Grid container alignItems='center' wrap='nowrap' spacing={16}>
-            <Grid item>{renderIcon(item)}</Grid>
-            <Grid item>
-                <Typography color='primary' style={{ width: 'auto', cursor: 'pointer' }} onClick={() => dispatch<any>(navFunc(item.uuid))}>
-=======
 } from "components/icon/icon";
 import { formatDate, formatFileSize, formatTime } from "common/formatters";
 import { resourceLabel } from "common/labels";
@@ -120,17 +70,12 @@ const renderName = (dispatch: Dispatch, item: GroupContentsResource) => {
                     style={{ width: "auto", cursor: "pointer" }}
                     onClick={() => dispatch<any>(navFunc(item.uuid))}
                 >
->>>>>>> main
                     {item.kind === ResourceKind.PROJECT || item.kind === ResourceKind.COLLECTION ? <IllegalNamingWarning name={item.name} /> : null}
                     {item.name}
                 </Typography>
             </Grid>
             <Grid item>
-<<<<<<< HEAD
-                <Typography variant='caption'>
-=======
                 <Typography variant="caption">
->>>>>>> main
                     <FavoriteStar resourceUuid={item.uuid} />
                     <PublicFavoriteStar resourceUuid={item.uuid} />
                     {item.kind === ResourceKind.PROJECT && <FrozenProject item={item} />}
@@ -150,17 +95,12 @@ const FrozenProject = (props: { item: ProjectResource }) => {
 
     if (props.item.frozenByUuid) {
         return (
-<<<<<<< HEAD
-            <Tooltip onOpen={getFullName} enterDelay={500} title={<span>Project was frozen by {fullUsername}</span>}>
-                <FreezeIcon style={{ fontSize: 'inherit' }} />
-=======
             <Tooltip
                 onOpen={getFullName}
                 enterDelay={500}
                 title={<span>Project was frozen by {fullUsername}</span>}
             >
                 <FreezeIcon style={{ fontSize: "inherit" }} />
->>>>>>> main
             </Tooltip>
         );
     } else {
@@ -196,26 +136,16 @@ const renderIcon = (item: GroupContentsResource) => {
 
 const renderDate = (date?: string) => {
     return (
-<<<<<<< HEAD
-        <Typography noWrap style={{ minWidth: '100px' }}>
-=======
         <Typography
             noWrap
             style={{ minWidth: "100px" }}
         >
->>>>>>> main
             {formatDate(date)}
         </Typography>
     );
 };
 
 const renderWorkflowName = (item: WorkflowResource) => (
-<<<<<<< HEAD
-    <Grid container alignItems='center' wrap='nowrap' spacing={16}>
-        <Grid item>{renderIcon(item)}</Grid>
-        <Grid item>
-            <Typography color='primary' style={{ width: '100px' }}>
-=======
     <Grid
         container
         alignItems="center"
@@ -228,7 +158,6 @@ const renderWorkflowName = (item: WorkflowResource) => (
                 color="primary"
                 style={{ width: "100px" }}
             >
->>>>>>> main
                 {item.name}
             </Typography>
         </Grid>
@@ -249,11 +178,7 @@ const resourceShare = (dispatch: Dispatch, uuidPrefix: string, ownerUuid?: strin
     return (
         <div>
             {!isPublic && uuid && (
-<<<<<<< HEAD
-                <Tooltip title='Share'>
-=======
                 <Tooltip title="Share">
->>>>>>> main
                     <IconButton onClick={() => dispatch<any>(openSharingDialog(uuid))}>
                         <ShareIcon />
                     </IconButton>
@@ -267,13 +192,8 @@ export const ResourceShare = connect((state: RootState, props: { uuid: string })
     const resource = getResource<WorkflowResource>(props.uuid)(state.resources);
     const uuidPrefix = getUuidPrefix(state);
     return {
-<<<<<<< HEAD
-        uuid: resource ? resource.uuid : '',
-        ownerUuid: resource ? resource.ownerUuid : '',
-=======
         uuid: resource ? resource.uuid : "",
         ownerUuid: resource ? resource.ownerUuid : "",
->>>>>>> main
         uuidPrefix,
     };
 })((props: { ownerUuid?: string; uuidPrefix: string; uuid?: string } & DispatchProp<any>) =>
@@ -287,26 +207,13 @@ const renderFirstName = (item: { firstName: string }) => {
 
 export const ResourceFirstName = connect((state: RootState, props: { uuid: string }) => {
     const resource = getResource<UserResource>(props.uuid)(state.resources);
-<<<<<<< HEAD
-    return resource || { firstName: '' };
-=======
     return resource || { firstName: "" };
->>>>>>> main
 })(renderFirstName);
 
 const renderLastName = (item: { lastName: string }) => <Typography noWrap>{item.lastName}</Typography>;
 
 export const ResourceLastName = connect((state: RootState, props: { uuid: string }) => {
     const resource = getResource<UserResource>(props.uuid)(state.resources);
-<<<<<<< HEAD
-    return resource || { lastName: '' };
-})(renderLastName);
-
-const renderFullName = (dispatch: Dispatch, item: { uuid: string; firstName: string; lastName: string }, link?: boolean) => {
-    const displayName = (item.firstName + ' ' + item.lastName).trim() || item.uuid;
-    return link ? (
-        <Typography noWrap color='primary' style={{ cursor: 'pointer' }} onClick={() => dispatch<any>(navigateToUserProfile(item.uuid))}>
-=======
     return resource || { lastName: "" };
 })(renderLastName);
 
@@ -319,7 +226,6 @@ const renderFullName = (dispatch: Dispatch, item: { uuid: string; firstName: str
             style={{ cursor: "pointer" }}
             onClick={() => dispatch<any>(navigateToUserProfile(item.uuid))}
         >
->>>>>>> main
             {displayName}
         </Typography>
     ) : (
@@ -329,40 +235,22 @@ const renderFullName = (dispatch: Dispatch, item: { uuid: string; firstName: str
 
 export const UserResourceFullName = connect((state: RootState, props: { uuid: string; link?: boolean }) => {
     const resource = getResource<UserResource>(props.uuid)(state.resources);
-<<<<<<< HEAD
-    return { item: resource || { uuid: '', firstName: '', lastName: '' }, link: props.link };
-=======
     return { item: resource || { uuid: "", firstName: "", lastName: "" }, link: props.link };
->>>>>>> main
 })((props: { item: { uuid: string; firstName: string; lastName: string }; link?: boolean } & DispatchProp<any>) =>
     renderFullName(props.dispatch, props.item, props.link)
 );
 
 const renderUuid = (item: { uuid: string }) => (
-<<<<<<< HEAD
-    <Typography data-cy='uuid' noWrap>
-        {item.uuid}
-        {(item.uuid && <CopyToClipboardSnackbar value={item.uuid} />) || '-'}
-=======
     <Typography
         data-cy="uuid"
         noWrap
     >
         {item.uuid}
         {(item.uuid && <CopyToClipboardSnackbar value={item.uuid} />) || "-"}
->>>>>>> main
     </Typography>
 );
 
 const renderUuidCopyIcon = (item: { uuid: string }) => (
-<<<<<<< HEAD
-    <Typography data-cy='uuid' noWrap>
-        {(item.uuid && <CopyToClipboardSnackbar value={item.uuid} />) || '-'}
-    </Typography>
-);
-
-export const ResourceUuid = connect((state: RootState, props: { uuid: string }) => getResource<UserResource>(props.uuid)(state.resources) || { uuid: '' })(renderUuid);
-=======
     <Typography
         data-cy="uuid"
         noWrap
@@ -374,26 +262,11 @@ export const ResourceUuid = connect((state: RootState, props: { uuid: string })
 export const ResourceUuid = connect(
     (state: RootState, props: { uuid: string }) => getResource<UserResource>(props.uuid)(state.resources) || { uuid: "" }
 )(renderUuid);
->>>>>>> main
 
 const renderEmail = (item: { email: string }) => <Typography noWrap>{item.email}</Typography>;
 
 export const ResourceEmail = connect((state: RootState, props: { uuid: string }) => {
     const resource = getResource<UserResource>(props.uuid)(state.resources);
-<<<<<<< HEAD
-    return resource || { email: '' };
-})(renderEmail);
-
-enum UserAccountStatus {
-    ACTIVE = 'Active',
-    INACTIVE = 'Inactive',
-    SETUP = 'Setup',
-    UNKNOWN = '',
-}
-
-const renderAccountStatus = (props: { status: UserAccountStatus }) => (
-    <Grid container alignItems='center' wrap='nowrap' spacing={8} data-cy='account-status'>
-=======
     return resource || { email: "" };
 })(renderEmail);
 
@@ -412,24 +285,15 @@ const renderAccountStatus = (props: { status: UserAccountStatus }) => (
         spacing={8}
         data-cy="account-status"
     >
->>>>>>> main
         <Grid item>
             {(() => {
                 switch (props.status) {
                     case UserAccountStatus.ACTIVE:
-<<<<<<< HEAD
-                        return <ActiveIcon style={{ color: '#4caf50', verticalAlign: 'middle' }} />;
-                    case UserAccountStatus.SETUP:
-                        return <SetupIcon style={{ color: '#2196f3', verticalAlign: 'middle' }} />;
-                    case UserAccountStatus.INACTIVE:
-                        return <InactiveIcon style={{ color: '#9e9e9e', verticalAlign: 'middle' }} />;
-=======
                         return <ActiveIcon style={{ color: "#4caf50", verticalAlign: "middle" }} />;
                     case UserAccountStatus.SETUP:
                         return <SetupIcon style={{ color: "#2196f3", verticalAlign: "middle" }} />;
                     case UserAccountStatus.INACTIVE:
                         return <InactiveIcon style={{ color: "#9e9e9e", verticalAlign: "middle" }} />;
->>>>>>> main
                     default:
                         return <></>;
                 }
@@ -481,19 +345,11 @@ const renderIsHidden = (props: {
     if (props.memberLinkUuid) {
         return (
             <Checkbox
-<<<<<<< HEAD
-                data-cy='user-visible-checkbox'
-                color='primary'
-                checked={props.visible}
-                disabled={!props.canManage}
-                onClick={(e) => {
-=======
                 data-cy="user-visible-checkbox"
                 color="primary"
                 checked={props.visible}
                 disabled={!props.canManage}
                 onClick={e => {
->>>>>>> main
                     e.stopPropagation();
                     props.setMemberIsHidden(props.memberLinkUuid, props.permissionLinkUuid, !props.visible);
                 }}
@@ -525,18 +381,14 @@ export const ResourceLinkTailIsVisible = connect(
 
         return member?.kind === ResourceKind.USER
             ? { memberLinkUuid: link?.uuid, permissionLinkUuid, visible: isVisible, canManage: !isBuiltin }
-<<<<<<< HEAD
-            : { memberLinkUuid: '', permissionLinkUuid: '', visible: false, canManage: false };
-=======
             : { memberLinkUuid: "", permissionLinkUuid: "", visible: false, canManage: false };
->>>>>>> main
     },
     { setMemberIsHidden }
 )(renderIsHidden);
 
 const renderIsAdmin = (props: { uuid: string; isAdmin: boolean; toggleIsAdmin: (uuid: string) => void }) => (
     <Checkbox
-        color='primary'
+        color="primary"
         checked={props.isAdmin}
         onClick={e => {
             e.stopPropagation();
@@ -557,11 +409,7 @@ const renderUsername = (item: { username: string; uuid: string }) => <Typography
 
 export const ResourceUsername = connect((state: RootState, props: { uuid: string }) => {
     const resource = getResource<UserResource>(props.uuid)(state.resources);
-<<<<<<< HEAD
-    return resource || { username: '', uuid: props.uuid };
-=======
     return resource || { username: "", uuid: props.uuid };
->>>>>>> main
 })(renderUsername);
 
 // Virtual machine resource
@@ -570,26 +418,16 @@ const renderHostname = (item: { hostname: string }) => <Typography noWrap>{item.
 
 export const VirtualMachineHostname = connect((state: RootState, props: { uuid: string }) => {
     const resource = getResource<VirtualMachinesResource>(props.uuid)(state.resources);
-<<<<<<< HEAD
-    return resource || { hostname: '' };
-=======
     return resource || { hostname: "" };
->>>>>>> main
 })(renderHostname);
 
 const renderVirtualMachineLogin = (login: { user: string }) => <Typography noWrap>{login.user}</Typography>;
 
 export const VirtualMachineLogin = connect((state: RootState, props: { linkUuid: string }) => {
     const permission = getResource<LinkResource>(props.linkUuid)(state.resources);
-<<<<<<< HEAD
-    const user = getResource<UserResource>(permission?.tailUuid || '')(state.resources);
-
-    return { user: user?.username || permission?.tailUuid || '' };
-=======
     const user = getResource<UserResource>(permission?.tailUuid || "")(state.resources);
 
     return { user: user?.username || permission?.tailUuid || "" };
->>>>>>> main
 })(renderVirtualMachineLogin);
 
 // Common methods
@@ -619,37 +457,21 @@ export const TokenScopes = withResourceData("scopes", renderCommonData);
 export const TokenUserId = withResourceData("userId", renderCommonData);
 
 const clusterColors = [
-<<<<<<< HEAD
-    ['#f44336', '#fff'],
-    ['#2196f3', '#fff'],
-    ['#009688', '#fff'],
-    ['#cddc39', '#fff'],
-    ['#ff9800', '#fff'],
-=======
     ["#f44336", "#fff"],
     ["#2196f3", "#fff"],
     ["#009688", "#fff"],
     ["#cddc39", "#fff"],
     ["#ff9800", "#fff"],
->>>>>>> main
 ];
 
 export const ResourceCluster = (props: { uuid: string }) => {
     const CLUSTER_ID_LENGTH = 5;
-<<<<<<< HEAD
-    const pos = props.uuid.length > CLUSTER_ID_LENGTH ? props.uuid.indexOf('-') : 5;
-    const clusterId = pos >= CLUSTER_ID_LENGTH ? props.uuid.substring(0, pos) : '';
-    const ci =
-        pos >= CLUSTER_ID_LENGTH
-            ? ((props.uuid.charCodeAt(0) * props.uuid.charCodeAt(1) + props.uuid.charCodeAt(2)) * props.uuid.charCodeAt(3) + props.uuid.charCodeAt(4)) %
-=======
     const pos = props.uuid.length > CLUSTER_ID_LENGTH ? props.uuid.indexOf("-") : 5;
     const clusterId = pos >= CLUSTER_ID_LENGTH ? props.uuid.substring(0, pos) : "";
     const ci =
         pos >= CLUSTER_ID_LENGTH
             ? ((props.uuid.charCodeAt(0) * props.uuid.charCodeAt(1) + props.uuid.charCodeAt(2)) * props.uuid.charCodeAt(3) +
                   props.uuid.charCodeAt(4)) %
->>>>>>> main
               clusterColors.length
             : 0;
     return (
@@ -657,11 +479,7 @@ export const ResourceCluster = (props: { uuid: string }) => {
             style={{
                 backgroundColor: clusterColors[ci][0],
                 color: clusterColors[ci][1],
-<<<<<<< HEAD
-                padding: '2px 7px',
-=======
                 padding: "2px 7px",
->>>>>>> main
                 borderRadius: 3,
             }}
         >
@@ -671,38 +489,22 @@ export const ResourceCluster = (props: { uuid: string }) => {
 };
 
 // Links Resources
-<<<<<<< HEAD
-const renderLinkName = (item: { name: string }) => <Typography noWrap>{item.name || '-'}</Typography>;
-
-export const ResourceLinkName = connect((state: RootState, props: { uuid: string }) => {
-    const resource = getResource<LinkResource>(props.uuid)(state.resources);
-    return resource || { name: '' };
-=======
 const renderLinkName = (item: { name: string }) => <Typography noWrap>{item.name || "-"}</Typography>;
 
 export const ResourceLinkName = connect((state: RootState, props: { uuid: string }) => {
     const resource = getResource<LinkResource>(props.uuid)(state.resources);
     return resource || { name: "" };
->>>>>>> main
 })(renderLinkName);
 
 const renderLinkClass = (item: { linkClass: string }) => <Typography noWrap>{item.linkClass}</Typography>;
 
 export const ResourceLinkClass = connect((state: RootState, props: { uuid: string }) => {
     const resource = getResource<LinkResource>(props.uuid)(state.resources);
-<<<<<<< HEAD
-    return resource || { linkClass: '' };
-})(renderLinkClass);
-
-const getResourceDisplayName = (resource: Resource): string => {
-    if ((resource as UserResource).kind === ResourceKind.USER && typeof (resource as UserResource).firstName !== 'undefined') {
-=======
     return resource || { linkClass: "" };
 })(renderLinkClass);
 
 const getResourceDisplayName = (resource: Resource): string => {
     if ((resource as UserResource).kind === ResourceKind.USER && typeof (resource as UserResource).firstName !== "undefined") {
->>>>>>> main
         // We can be sure the resource is UserResource
         return getUserDisplayName(resource as UserResource);
     } else {
@@ -714,10 +516,6 @@ const renderResourceLink = (dispatch: Dispatch, item: Resource) => {
     var displayName = getResourceDisplayName(item);
 
     return (
-<<<<<<< HEAD
-        <Typography noWrap color='primary' style={{ cursor: 'pointer' }} onClick={() => dispatch<any>(navigateTo(item.uuid))}>
-            {resourceLabel(item.kind, item && item.kind === ResourceKind.GROUP ? (item as GroupResource).groupClass || '' : '')}: {displayName || item.uuid}
-=======
         <Typography
             noWrap
             color="primary"
@@ -731,103 +529,64 @@ const renderResourceLink = (dispatch: Dispatch, item: Resource) => {
         >
             {resourceLabel(item.kind, item && item.kind === ResourceKind.GROUP ? (item as GroupResource).groupClass || "" : "")}:{" "}
             {displayName || item.uuid}
->>>>>>> main
         </Typography>
     );
 };
 
 export const ResourceLinkTail = connect((state: RootState, props: { uuid: string }) => {
     const resource = getResource<LinkResource>(props.uuid)(state.resources);
-<<<<<<< HEAD
-    const tailResource = getResource<Resource>(resource?.tailUuid || '')(state.resources);
-
-    return {
-        item: tailResource || { uuid: resource?.tailUuid || '', kind: resource?.tailKind || ResourceKind.NONE },
-=======
     const tailResource = getResource<Resource>(resource?.tailUuid || "")(state.resources);
 
     return {
         item: tailResource || { uuid: resource?.tailUuid || "", kind: resource?.tailKind || ResourceKind.NONE },
->>>>>>> main
     };
 })((props: { item: Resource } & DispatchProp<any>) => renderResourceLink(props.dispatch, props.item));
 
 export const ResourceLinkHead = connect((state: RootState, props: { uuid: string }) => {
     const resource = getResource<LinkResource>(props.uuid)(state.resources);
-<<<<<<< HEAD
-    const headResource = getResource<Resource>(resource?.headUuid || '')(state.resources);
-
-    return {
-        item: headResource || { uuid: resource?.headUuid || '', kind: resource?.headKind || ResourceKind.NONE },
-=======
     const headResource = getResource<Resource>(resource?.headUuid || "")(state.resources);
 
     return {
         item: headResource || { uuid: resource?.headUuid || "", kind: resource?.headKind || ResourceKind.NONE },
->>>>>>> main
     };
 })((props: { item: Resource } & DispatchProp<any>) => renderResourceLink(props.dispatch, props.item));
 
 export const ResourceLinkUuid = connect((state: RootState, props: { uuid: string }) => {
     const resource = getResource<LinkResource>(props.uuid)(state.resources);
-<<<<<<< HEAD
-    return resource || { uuid: '' };
-=======
     return resource || { uuid: "" };
->>>>>>> main
 })(renderUuid);
 
 export const ResourceLinkHeadUuid = connect((state: RootState, props: { uuid: string }) => {
     const link = getResource<LinkResource>(props.uuid)(state.resources);
-<<<<<<< HEAD
-    const headResource = getResource<Resource>(link?.headUuid || '')(state.resources);
-
-    return headResource || { uuid: '' };
-=======
     const headResource = getResource<Resource>(link?.headUuid || "")(state.resources);
 
     return headResource || { uuid: "" };
->>>>>>> main
 })(renderUuid);
 
 export const ResourceLinkTailUuid = connect((state: RootState, props: { uuid: string }) => {
     const link = getResource<LinkResource>(props.uuid)(state.resources);
-<<<<<<< HEAD
-    const tailResource = getResource<Resource>(link?.tailUuid || '')(state.resources);
-
-    return tailResource || { uuid: '' };
-=======
     const tailResource = getResource<Resource>(link?.tailUuid || "")(state.resources);
 
     return tailResource || { uuid: "" };
->>>>>>> main
 })(renderUuid);
 
 const renderLinkDelete = (dispatch: Dispatch, item: LinkResource, canManage: boolean) => {
     if (item.uuid) {
         return canManage ? (
             <Typography noWrap>
-<<<<<<< HEAD
-                <IconButton data-cy='resource-delete-button' onClick={() => dispatch<any>(openRemoveGroupMemberDialog(item.uuid))}>
-=======
                 <IconButton
                     data-cy="resource-delete-button"
                     onClick={() => dispatch<any>(openRemoveGroupMemberDialog(item.uuid))}
                 >
->>>>>>> main
                     <RemoveIcon />
                 </IconButton>
             </Typography>
         ) : (
             <Typography noWrap>
-<<<<<<< HEAD
-                <IconButton disabled data-cy='resource-delete-button'>
-=======
                 <IconButton
                     disabled
                     data-cy="resource-delete-button"
                 >
->>>>>>> main
                     <RemoveIcon />
                 </IconButton>
             </Typography>
@@ -839,45 +598,26 @@ const renderLinkDelete = (dispatch: Dispatch, item: LinkResource, canManage: boo
 
 export const ResourceLinkDelete = connect((state: RootState, props: { uuid: string }) => {
     const link = getResource<LinkResource>(props.uuid)(state.resources);
-<<<<<<< HEAD
-    const isBuiltin = isBuiltinGroup(link?.headUuid || '') || isBuiltinGroup(link?.tailUuid || '');
-
-    return {
-        item: link || { uuid: '', kind: ResourceKind.NONE },
-=======
     const isBuiltin = isBuiltinGroup(link?.headUuid || "") || isBuiltinGroup(link?.tailUuid || "");
 
     return {
         item: link || { uuid: "", kind: ResourceKind.NONE },
->>>>>>> main
         canManage: link && getResourceLinkCanManage(state, link) && !isBuiltin,
     };
 })((props: { item: LinkResource; canManage: boolean } & DispatchProp<any>) => renderLinkDelete(props.dispatch, props.item, props.canManage));
 
 export const ResourceLinkTailEmail = connect((state: RootState, props: { uuid: string }) => {
     const link = getResource<LinkResource>(props.uuid)(state.resources);
-<<<<<<< HEAD
-    const resource = getResource<UserResource>(link?.tailUuid || '')(state.resources);
-
-    return resource || { email: '' };
-=======
     const resource = getResource<UserResource>(link?.tailUuid || "")(state.resources);
 
     return resource || { email: "" };
->>>>>>> main
 })(renderEmail);
 
 export const ResourceLinkTailUsername = connect((state: RootState, props: { uuid: string }) => {
     const link = getResource<LinkResource>(props.uuid)(state.resources);
-<<<<<<< HEAD
-    const resource = getResource<UserResource>(link?.tailUuid || '')(state.resources);
-
-    return resource || { username: '' };
-=======
     const resource = getResource<UserResource>(link?.tailUuid || "")(state.resources);
 
     return resource || { username: "" };
->>>>>>> main
 })(renderUsername);
 
 const renderPermissionLevel = (dispatch: Dispatch, link: LinkResource, canManage: boolean) => {
@@ -885,13 +625,6 @@ const renderPermissionLevel = (dispatch: Dispatch, link: LinkResource, canManage
         <Typography noWrap>
             {formatPermissionLevel(link.name as PermissionLevel)}
             {canManage ? (
-<<<<<<< HEAD
-                <IconButton data-cy='edit-permission-button' onClick={(event) => dispatch<any>(openPermissionEditContextMenu(event, link))}>
-                    <RenameIcon />
-                </IconButton>
-            ) : (
-                ''
-=======
                 <IconButton
                     data-cy="edit-permission-button"
                     onClick={event => dispatch<any>(openPermissionEditContextMenu(event, link))}
@@ -900,7 +633,6 @@ const renderPermissionLevel = (dispatch: Dispatch, link: LinkResource, canManage
                 </IconButton>
             ) : (
                 ""
->>>>>>> main
             )}
         </Typography>
     );
@@ -908,34 +640,20 @@ const renderPermissionLevel = (dispatch: Dispatch, link: LinkResource, canManage
 
 export const ResourceLinkHeadPermissionLevel = connect((state: RootState, props: { uuid: string }) => {
     const link = getResource<LinkResource>(props.uuid)(state.resources);
-<<<<<<< HEAD
-    const isBuiltin = isBuiltinGroup(link?.headUuid || '') || isBuiltinGroup(link?.tailUuid || '');
-
-    return {
-        link: link || { uuid: '', name: '', kind: ResourceKind.NONE },
-=======
     const isBuiltin = isBuiltinGroup(link?.headUuid || "") || isBuiltinGroup(link?.tailUuid || "");
 
     return {
         link: link || { uuid: "", name: "", kind: ResourceKind.NONE },
->>>>>>> main
         canManage: link && getResourceLinkCanManage(state, link) && !isBuiltin,
     };
 })((props: { link: LinkResource; canManage: boolean } & DispatchProp<any>) => renderPermissionLevel(props.dispatch, props.link, props.canManage));
 
 export const ResourceLinkTailPermissionLevel = connect((state: RootState, props: { uuid: string }) => {
     const link = getResource<LinkResource>(props.uuid)(state.resources);
-<<<<<<< HEAD
-    const isBuiltin = isBuiltinGroup(link?.headUuid || '') || isBuiltinGroup(link?.tailUuid || '');
-
-    return {
-        link: link || { uuid: '', name: '', kind: ResourceKind.NONE },
-=======
     const isBuiltin = isBuiltinGroup(link?.headUuid || "") || isBuiltinGroup(link?.tailUuid || "");
 
     return {
         link: link || { uuid: "", name: "", kind: ResourceKind.NONE },
->>>>>>> main
         canManage: link && getResourceLinkCanManage(state, link) && !isBuiltin,
     };
 })((props: { link: LinkResource; canManage: boolean } & DispatchProp<any>) => renderPermissionLevel(props.dispatch, props.link, props.canManage));
@@ -955,11 +673,7 @@ const resourceRunProcess = (dispatch: Dispatch, uuid: string) => {
     return (
         <div>
             {uuid && (
-<<<<<<< HEAD
-                <Tooltip title='Run process'>
-=======
                 <Tooltip title="Run process">
->>>>>>> main
                     <IconButton onClick={() => dispatch<any>(openRunProcess(uuid))}>
                         <ProcessIcon />
                     </IconButton>
@@ -972,11 +686,7 @@ const resourceRunProcess = (dispatch: Dispatch, uuid: string) => {
 export const ResourceRunProcess = connect((state: RootState, props: { uuid: string }) => {
     const resource = getResource<WorkflowResource>(props.uuid)(state.resources);
     return {
-<<<<<<< HEAD
-        uuid: resource ? resource.uuid : '',
-=======
         uuid: resource ? resource.uuid : "",
->>>>>>> main
     };
 })((props: { uuid: string } & DispatchProp<any>) => resourceRunProcess(props.dispatch, props.uuid));
 
@@ -989,14 +699,10 @@ const renderWorkflowStatus = (uuidPrefix: string, ownerUuid?: string) => {
 };
 
 const renderStatus = (status: string) => (
-<<<<<<< HEAD
-    <Typography noWrap style={{ width: '60px' }}>
-=======
     <Typography
         noWrap
         style={{ width: "60px" }}
     >
->>>>>>> main
         {status}
     </Typography>
 );
@@ -1005,47 +711,24 @@ export const ResourceWorkflowStatus = connect((state: RootState, props: { uuid:
     const resource = getResource<WorkflowResource>(props.uuid)(state.resources);
     const uuidPrefix = getUuidPrefix(state);
     return {
-<<<<<<< HEAD
-        ownerUuid: resource ? resource.ownerUuid : '',
-=======
         ownerUuid: resource ? resource.ownerUuid : "",
->>>>>>> main
         uuidPrefix,
     };
 })((props: { ownerUuid?: string; uuidPrefix: string }) => renderWorkflowStatus(props.uuidPrefix, props.ownerUuid));
 
 export const ResourceContainerUuid = connect((state: RootState, props: { uuid: string }) => {
     const process = getProcess(props.uuid)(state.resources);
-<<<<<<< HEAD
-    return { uuid: process?.container?.uuid ? process?.container?.uuid : '' };
-})((props: { uuid: string }) => renderUuid({ uuid: props.uuid }));
-
-enum ColumnSelection {
-    OUTPUT_UUID = 'outputUuid',
-    LOG_UUID = 'logUuid',
-=======
     return { uuid: process?.container?.uuid ? process?.container?.uuid : "" };
 })((props: { uuid: string }) => renderUuid({ uuid: props.uuid }));
 
 enum ColumnSelection {
     OUTPUT_UUID = "outputUuid",
     LOG_UUID = "logUuid",
->>>>>>> main
 }
 
 const renderUuidLinkWithCopyIcon = (dispatch: Dispatch, item: ProcessResource, column: string) => {
     const selectedColumnUuid = item[column];
     return (
-<<<<<<< HEAD
-        <Grid container alignItems='center' wrap='nowrap'>
-            <Grid item>
-                {selectedColumnUuid ? (
-                    <Typography color='primary' style={{ width: 'auto', cursor: 'pointer' }} noWrap onClick={() => dispatch<any>(navigateTo(selectedColumnUuid))}>
-                        {selectedColumnUuid}
-                    </Typography>
-                ) : (
-                    '-'
-=======
         <Grid
             container
             alignItems="center"
@@ -1063,7 +746,6 @@ const renderUuidLinkWithCopyIcon = (dispatch: Dispatch, item: ProcessResource, c
                     </Typography>
                 ) : (
                     "-"
->>>>>>> main
                 )}
             </Grid>
             <Grid item>{selectedColumnUuid && renderUuidCopyIcon({ uuid: selectedColumnUuid })}</Grid>
@@ -1083,58 +765,31 @@ export const ResourceLogUuid = connect((state: RootState, props: { uuid: string
 
 export const ResourceParentProcess = connect((state: RootState, props: { uuid: string }) => {
     const process = getProcess(props.uuid)(state.resources);
-<<<<<<< HEAD
-    return { parentProcess: process?.containerRequest?.requestingContainerUuid || '' };
-=======
     return { parentProcess: process?.containerRequest?.requestingContainerUuid || "" };
->>>>>>> main
 })((props: { parentProcess: string }) => renderUuid({ uuid: props.parentProcess }));
 
 export const ResourceModifiedByUserUuid = connect((state: RootState, props: { uuid: string }) => {
     const process = getProcess(props.uuid)(state.resources);
-<<<<<<< HEAD
-    return { userUuid: process?.containerRequest?.modifiedByUserUuid || '' };
-=======
     return { userUuid: process?.containerRequest?.modifiedByUserUuid || "" };
->>>>>>> main
 })((props: { userUuid: string }) => renderUuid({ uuid: props.userUuid }));
 
 export const ResourceCreatedAtDate = connect((state: RootState, props: { uuid: string }) => {
     const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
-<<<<<<< HEAD
-    return { date: resource ? resource.createdAt : '' };
-=======
     return { date: resource ? resource.createdAt : "" };
->>>>>>> main
 })((props: { date: string }) => renderDate(props.date));
 
 export const ResourceLastModifiedDate = connect((state: RootState, props: { uuid: string }) => {
     const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
-<<<<<<< HEAD
-    return { date: resource ? resource.modifiedAt : '' };
-=======
     return { date: resource ? resource.modifiedAt : "" };
->>>>>>> main
 })((props: { date: string }) => renderDate(props.date));
 
 export const ResourceTrashDate = connect((state: RootState, props: { uuid: string }) => {
     const resource = getResource<TrashableResource>(props.uuid)(state.resources);
-<<<<<<< HEAD
-    return { date: resource ? resource.trashAt : '' };
-=======
     return { date: resource ? resource.trashAt : "" };
->>>>>>> main
 })((props: { date: string }) => renderDate(props.date));
 
 export const ResourceDeleteDate = connect((state: RootState, props: { uuid: string }) => {
     const resource = getResource<TrashableResource>(props.uuid)(state.resources);
-<<<<<<< HEAD
-    return { date: resource ? resource.deleteAt : '' };
-})((props: { date: string }) => renderDate(props.date));
-
-export const renderFileSize = (fileSize?: number) => (
-    <Typography noWrap style={{ minWidth: '45px' }}>
-=======
     return { date: resource ? resource.deleteAt : "" };
 })((props: { date: string }) => renderDate(props.date));
 
@@ -1143,7 +798,6 @@ export const renderFileSize = (fileSize?: number) => (
         noWrap
         style={{ minWidth: "45px" }}
     >
->>>>>>> main
         {formatFileSize(fileSize)}
     </Typography>
 );
@@ -1152,66 +806,38 @@ export const ResourceFileSize = connect((state: RootState, props: { uuid: string
     const resource = getResource<CollectionResource>(props.uuid)(state.resources);
 
     if (resource && resource.kind !== ResourceKind.COLLECTION) {
-<<<<<<< HEAD
-        return { fileSize: '' };
-=======
         return { fileSize: "" };
->>>>>>> main
     }
 
     return { fileSize: resource ? resource.fileSizeTotal : 0 };
 })((props: { fileSize?: number }) => renderFileSize(props.fileSize));
 
-<<<<<<< HEAD
-const renderOwner = (owner: string) => <Typography noWrap>{owner || '-'}</Typography>;
-
-export const ResourceOwner = connect((state: RootState, props: { uuid: string }) => {
-    const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
-    return { owner: resource ? resource.ownerUuid : '' };
-=======
 const renderOwner = (owner: string) => <Typography noWrap>{owner || "-"}</Typography>;
 
 export const ResourceOwner = connect((state: RootState, props: { uuid: string }) => {
     const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
     return { owner: resource ? resource.ownerUuid : "" };
->>>>>>> main
 })((props: { owner: string }) => renderOwner(props.owner));
 
 export const ResourceOwnerName = connect((state: RootState, props: { uuid: string }) => {
     const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
     const ownerNameState = state.ownerName;
-<<<<<<< HEAD
-    const ownerName = ownerNameState.find((it) => it.uuid === resource!.ownerUuid);
-=======
     const ownerName = ownerNameState.find(it => it.uuid === resource!.ownerUuid);
->>>>>>> main
     return { owner: ownerName ? ownerName!.name : resource!.ownerUuid };
 })((props: { owner: string }) => renderOwner(props.owner));
 
 export const ResourceUUID = connect((state: RootState, props: { uuid: string }) => {
     const resource = getResource<CollectionResource>(props.uuid)(state.resources);
-<<<<<<< HEAD
-    return { uuid: resource ? resource.uuid : '' };
-})((props: { uuid: string }) => renderUuid({ uuid: props.uuid }));
-
-const renderVersion = (version: number) => {
-    return <Typography>{version ?? '-'}</Typography>;
-=======
     return { uuid: resource ? resource.uuid : "" };
 })((props: { uuid: string }) => renderUuid({ uuid: props.uuid }));
 
 const renderVersion = (version: number) => {
     return <Typography>{version ?? "-"}</Typography>;
->>>>>>> main
 };
 
 export const ResourceVersion = connect((state: RootState, props: { uuid: string }) => {
     const resource = getResource<CollectionResource>(props.uuid)(state.resources);
-<<<<<<< HEAD
-    return { version: resource ? resource.version : '' };
-=======
     return { version: resource ? resource.version : "" };
->>>>>>> main
 })((props: { version: number }) => renderVersion(props.version));
 
 const renderPortableDataHash = (portableDataHash: string | null) => (
@@ -1222,47 +848,27 @@ const renderPortableDataHash = (portableDataHash: string | null) => (
                 <CopyToClipboardSnackbar value={portableDataHash} />
             </>
         ) : (
-<<<<<<< HEAD
-            '-'
-=======
             "-"
->>>>>>> main
         )}
     </Typography>
 );
 
 export const ResourcePortableDataHash = connect((state: RootState, props: { uuid: string }) => {
     const resource = getResource<CollectionResource>(props.uuid)(state.resources);
-<<<<<<< HEAD
-    return { portableDataHash: resource ? resource.portableDataHash : '' };
-})((props: { portableDataHash: string }) => renderPortableDataHash(props.portableDataHash));
-
-const renderFileCount = (fileCount: number) => {
-    return <Typography>{fileCount ?? '-'}</Typography>;
-=======
     return { portableDataHash: resource ? resource.portableDataHash : "" };
 })((props: { portableDataHash: string }) => renderPortableDataHash(props.portableDataHash));
 
 const renderFileCount = (fileCount: number) => {
     return <Typography>{fileCount ?? "-"}</Typography>;
->>>>>>> main
 };
 
 export const ResourceFileCount = connect((state: RootState, props: { uuid: string }) => {
     const resource = getResource<CollectionResource>(props.uuid)(state.resources);
-<<<<<<< HEAD
-    return { fileCount: resource ? resource.fileCount : '' };
-})((props: { fileCount: number }) => renderFileCount(props.fileCount));
-
-const userFromID = connect((state: RootState, props: { uuid: string }) => {
-    let userFullname = '';
-=======
     return { fileCount: resource ? resource.fileCount : "" };
 })((props: { fileCount: number }) => renderFileCount(props.fileCount));
 
 const userFromID = connect((state: RootState, props: { uuid: string }) => {
     let userFullname = "";
->>>>>>> main
     const resource = getResource<GroupContentsResource & UserResource>(props.uuid)(state.resources);
 
     if (resource) {
@@ -1275,11 +881,7 @@ const userFromID = connect((state: RootState, props: { uuid: string }) => {
 const ownerFromResourceId = compose(
     connect((state: RootState, props: { uuid: string }) => {
         const childResource = getResource<GroupContentsResource & UserResource>(props.uuid)(state.resources);
-<<<<<<< HEAD
-        return { uuid: childResource ? (childResource as Resource).ownerUuid : '' };
-=======
         return { uuid: childResource ? (childResource as Resource).ownerUuid : "" };
->>>>>>> main
     }),
     userFromID
 );
@@ -1289,12 +891,6 @@ const _resourceWithName = withStyles(
     { withTheme: true }
 )((props: { uuid: string; userFullname: string; dispatch: Dispatch; theme: ArvadosTheme }) => {
     const { uuid, userFullname, dispatch, theme } = props;
-<<<<<<< HEAD
-    if (userFullname === '') {
-        dispatch<any>(loadResource(uuid, false));
-        return (
-            <Typography style={{ color: theme.palette.primary.main }} inline noWrap>
-=======
     if (userFullname === "") {
         dispatch<any>(loadResource(uuid, false));
         return (
@@ -1303,22 +899,17 @@ const _resourceWithName = withStyles(
                 inline
                 noWrap
             >
->>>>>>> main
                 {uuid}
             </Typography>
         );
     }
 
     return (
-<<<<<<< HEAD
-        <Typography style={{ color: theme.palette.primary.main }} inline noWrap>
-=======
         <Typography
             style={{ color: theme.palette.primary.main }}
             inline
             noWrap
         >
->>>>>>> main
             {userFullname} ({uuid})
         </Typography>
     );
@@ -1331,11 +922,7 @@ export const ResourceWithName = userFromID(_resourceWithName);
 export const UserNameFromID = compose(userFromID)((props: { uuid: string; displayAsText?: string; userFullname: string; dispatch: Dispatch }) => {
     const { uuid, userFullname, dispatch } = props;
 
-<<<<<<< HEAD
-    if (userFullname === '') {
-=======
     if (userFullname === "") {
->>>>>>> main
         dispatch<any>(loadResource(uuid, false));
     }
     return <span>{userFullname ? userFullname : uuid}</span>;
@@ -1343,15 +930,9 @@ export const UserNameFromID = compose(userFromID)((props: { uuid: string; displa
 
 export const ResponsiblePerson = compose(
     connect((state: RootState, props: { uuid: string; parentRef: HTMLElement | null }) => {
-<<<<<<< HEAD
-        let responsiblePersonName: string = '';
-        let responsiblePersonUUID: string = '';
-        let responsiblePersonProperty: string = '';
-=======
         let responsiblePersonName: string = "";
         let responsiblePersonUUID: string = "";
         let responsiblePersonProperty: string = "";
->>>>>>> main
 
         if (state.auth.config.clusterConfig.Collections.ManagedProperties) {
             let index = 0;
@@ -1359,11 +940,7 @@ export const ResponsiblePerson = compose(
 
             while (!responsiblePersonProperty && keys[index]) {
                 const key = keys[index];
-<<<<<<< HEAD
-                if (state.auth.config.clusterConfig.Collections.ManagedProperties[key].Function === 'original_owner') {
-=======
                 if (state.auth.config.clusterConfig.Collections.ManagedProperties[key].Function === "original_owner") {
->>>>>>> main
                     responsiblePersonProperty = key;
                 }
                 index++;
@@ -1388,45 +965,30 @@ export const ResponsiblePerson = compose(
     const { uuid, responsiblePersonName, parentRef, theme } = props;
 
     if (!uuid && parentRef) {
-<<<<<<< HEAD
-        parentRef.style.display = 'none';
-        return null;
-    } else if (parentRef) {
-        parentRef.style.display = 'block';
-=======
         parentRef.style.display = "none";
         return null;
     } else if (parentRef) {
         parentRef.style.display = "block";
->>>>>>> main
     }
 
     if (!responsiblePersonName) {
         return (
-<<<<<<< HEAD
-            <Typography style={{ color: theme.palette.primary.main }} inline noWrap>
-=======
             <Typography
                 style={{ color: theme.palette.primary.main }}
                 inline
                 noWrap
             >
->>>>>>> main
                 {uuid}
             </Typography>
         );
     }
 
     return (
-<<<<<<< HEAD
-        <Typography style={{ color: theme.palette.primary.main }} inline noWrap>
-=======
         <Typography
             style={{ color: theme.palette.primary.main }}
             inline
             noWrap
         >
->>>>>>> main
             {responsiblePersonName} ({uuid})
         </Typography>
     );
@@ -1436,39 +998,27 @@ const renderType = (type: string, subtype: string) => <Typography noWrap>{resour
 
 export const ResourceType = connect((state: RootState, props: { uuid: string }) => {
     const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
-<<<<<<< HEAD
-    return { type: resource ? resource.kind : '', subtype: resource && resource.kind === ResourceKind.GROUP ? resource.groupClass : '' };
-=======
     return { type: resource ? resource.kind : "", subtype: resource && resource.kind === ResourceKind.GROUP ? resource.groupClass : "" };
->>>>>>> main
 })((props: { type: string; subtype: string }) => renderType(props.type, props.subtype));
 
 export const ResourceStatus = connect((state: RootState, props: { uuid: string }) => {
     return { resource: getResource<GroupContentsResource>(props.uuid)(state.resources) };
 })((props: { resource: GroupContentsResource }) =>
-<<<<<<< HEAD
-    props.resource && props.resource.kind === ResourceKind.COLLECTION ? <CollectionStatus uuid={props.resource.uuid} /> : <ProcessStatus uuid={props.resource.uuid} />
-=======
     props.resource && props.resource.kind === ResourceKind.COLLECTION ? (
         <CollectionStatus uuid={props.resource.uuid} />
     ) : (
         <ProcessStatus uuid={props.resource.uuid} />
     )
->>>>>>> main
 );
 
 export const CollectionStatus = connect((state: RootState, props: { uuid: string }) => {
     return { collection: getResource<CollectionResource>(props.uuid)(state.resources) };
 })((props: { collection: CollectionResource }) =>
-<<<<<<< HEAD
-    props.collection.uuid !== props.collection.currentVersionUuid ? <Typography>version {props.collection.version}</Typography> : <Typography>head version</Typography>
-=======
     props.collection.uuid !== props.collection.currentVersionUuid ? (
         <Typography>version {props.collection.version}</Typography>
     ) : (
         <Typography>head version</Typography>
     )
->>>>>>> main
 );
 
 export const CollectionName = connect((state: RootState, props: { uuid: string; className?: string }) => {
@@ -1494,11 +1044,7 @@ export const ProcessStatus = compose(
                 height: props.theme.spacing.unit * 3,
                 width: props.theme.spacing.unit * 12,
                 ...getProcessStatusStyles(getProcessStatus(props.process), props.theme),
-<<<<<<< HEAD
-                fontSize: '0.875rem',
-=======
                 fontSize: "0.875rem",
->>>>>>> main
                 borderRadius: props.theme.spacing.unit * 0.625,
             }}
         />
@@ -1509,13 +1055,6 @@ export const ProcessStatus = compose(
 
 export const ProcessStartDate = connect((state: RootState, props: { uuid: string }) => {
     const process = getProcess(props.uuid)(state.resources);
-<<<<<<< HEAD
-    return { date: process && process.container ? process.container.startedAt : '' };
-})((props: { date: string }) => renderDate(props.date));
-
-export const renderRunTime = (time: number) => (
-    <Typography noWrap style={{ minWidth: '45px' }}>
-=======
     return { date: process && process.container ? process.container.startedAt : "" };
 })((props: { date: string }) => renderDate(props.date));
 
@@ -1524,7 +1063,6 @@ export const renderRunTime = (time: number) => (
         noWrap
         style={{ minWidth: "45px" }}
     >
->>>>>>> main
         {formatTime(time, true)}
     </Typography>
 );

commit e10e24d713e5c31cbc34efca458f5718eb6eb000
Merge: ff8bab30 c30fc0c7
Author: Lisa Knox <lisaknox83 at gmail.com>
Date:   Tue Oct 10 11:46:28 2023 -0400

    Merge branch 'main' into 15768-multi-select-operations Arvados-DCO-1.1-Signed-off-by: Lisa Knox <lisa.knox at curii.com>

diff --cc cypress/integration/collection.spec.js
index 8145ec3f,69e48417..35f36e1e
--- a/cypress/integration/collection.spec.js
+++ b/cypress/integration/collection.spec.js
@@@ -362,96 -368,119 +362,152 @@@ describe("Collection panel tests", func
                  cy.goToPath(`/collections/${this.testCollection.uuid}`);
  
                  const names = [
 -                    'bar', // initial name already set
 -                    '&',
 -                    'foo',
 -                    '&',
 -                    'I ❤️ ⛵️',
 -                    '...',
 -                    '#..',
 -                    'some name with whitespaces',
 -                    'some name with #2',
 -                    'is this name legal? I hope it is',
 -                    'some_file.pdf#',
 -                    'some_file.pdf?',
 -                    '?some_file.pdf',
 -                    'some%file.pdf',
 -                    'some%2Ffile.pdf',
 -                    'some%22file.pdf',
 -                    'some%20file.pdf',
 +                    "bar", // initial name already set
 +                    "&",
 +                    "foo",
 +                    "&",
 +                    "I ❤️ ⛵️",
 +                    "...",
 +                    "#..",
 +                    "some name with whitespaces",
 +                    "some name with #2",
 +                    "is this name legal? I hope it is",
 +                    "some_file.pdf#",
 +                    "some_file.pdf?",
 +                    "?some_file.pdf",
 +                    "some%file.pdf",
 +                    "some%2Ffile.pdf",
 +                    "some%22file.pdf",
 +                    "some%20file.pdf",
                      "G%C3%BCnter's%20file.pdf",
 -                    'table%&?*2',
 -                    'bar' // make sure we can go back to the original name as a last step
 +                    "table%&?*2",
 +                    "bar", // make sure we can go back to the original name as a last step
                  ];
 -                cy.intercept({method: 'PUT', url: '**/arvados/v1/collections/*'}).as('renameRequest');
++                cy.intercept({ method: "PUT", url: "**/arvados/v1/collections/*" }).as("renameRequest");
                  eachPair(names, (from, to) => {
 -                    cy.waitForDom().get('[data-cy=collection-files-panel]')
 -                        .contains(`${from}`).rightclick();
 -                    cy.get('[data-cy=context-menu]')
 -                        .contains('Rename')
 -                        .click();
 -                    cy.get('[data-cy=form-dialog]')
 -                        .should('contain', 'Rename')
 +                    cy.waitForDom().get("[data-cy=collection-files-panel]").contains(`${from}`).rightclick();
 +                    cy.get("[data-cy=context-menu]").contains("Rename").click();
 +                    cy.get("[data-cy=form-dialog]")
 +                        .should("contain", "Rename")
 +                        .within(() => {
 +                            cy.get("input").type("{selectall}{backspace}").type(to, { parseSpecialCharSequences: false });
 +                        });
 +                    cy.get("[data-cy=form-submit-btn]").click();
++                    cy.wait("@renameRequest");
 +                    cy.get("[data-cy=collection-files-panel]").should("not.contain", `${from}`).and("contain", `${to}`);
 +                });
 +            });
 +    });
 +
 +    it("renames a file to a different directory", function () {
 +        // Creates the collection using the admin token so we can set up
 +        // a bogus manifest text without block signatures.
 +        cy.createCollection(adminUser.token, {
 +            name: `Test collection ${Math.floor(Math.random() * 999999)}`,
 +            owner_uuid: activeUser.user.uuid,
 +            manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n",
 +        })
 +            .as("testCollection")
 +            .then(function () {
 +                cy.loginAs(activeUser);
 +                cy.goToPath(`/collections/${this.testCollection.uuid}`);
 +
 +                ["subdir", "G%C3%BCnter's%20file", "table%&?*2"].forEach(subdir => {
 +                    cy.waitForDom().get("[data-cy=collection-files-panel]").contains("bar").rightclick();
 +                    cy.get("[data-cy=context-menu]").contains("Rename").click();
 +                    cy.get("[data-cy=form-dialog]")
 +                        .should("contain", "Rename")
 +                        .within(() => {
 +                            cy.get("input").type(`{selectall}{backspace}${subdir}/foo`);
 +                        });
 +                    cy.get("[data-cy=form-submit-btn]").click();
 +                    cy.get("[data-cy=collection-files-panel]").should("not.contain", "bar").and("contain", subdir);
 +                    cy.get("[data-cy=collection-files-panel]").contains(subdir).click();
 +
 +                    // Rename 'subdir/foo' to 'bar'
 +                    cy.wait(1000);
 +                    cy.get("[data-cy=collection-files-panel]").contains("foo").rightclick();
 +                    cy.get("[data-cy=context-menu]").contains("Rename").click();
 +                    cy.get("[data-cy=form-dialog]")
 +                        .should("contain", "Rename")
                          .within(() => {
 -                            cy.get('input')
 -                                .type('{selectall}{backspace}')
 -                                .type(to, { parseSpecialCharSequences: false });
 +                            cy.get("input").should("have.value", `${subdir}/foo`).type(`{selectall}{backspace}bar`);
                          });
 -                    cy.get('[data-cy=form-submit-btn]').click();
 -                    cy.wait('@renameRequest');
 -                    cy.get('[data-cy=collection-files-panel]')
 -                        .should('not.contain', `${from}`)
 -                        .and('contain', `${to}`);
 -                })
 +                    cy.get("[data-cy=form-submit-btn]").click();
 +
 +                    // need to wait for dialog to dismiss
 +                    cy.get("[data-cy=form-dialog]").should("not.exist");
 +
 +                    cy.waitForDom().get("[data-cy=collection-files-panel]").contains("Home").click();
 +
 +                    cy.wait(2000);
 +                    cy.get("[data-cy=collection-files-panel]")
 +                        .should("contain", subdir) // empty dir kept
 +                        .and("contain", "bar");
 +
 +                    cy.get("[data-cy=collection-files-panel]").contains(subdir).rightclick();
 +                    cy.get("[data-cy=context-menu]").contains("Remove").click();
 +                    cy.get("[data-cy=confirmation-dialog-ok-btn]").click();
 +                    cy.get("[data-cy=form-dialog]").should("not.exist");
 +                });
              });
      });
  
 -    it('renames a file to a different directory', function () {
++    it("renames a file to a different directory", function () {
+         // Creates the collection using the admin token so we can set up
+         // a bogus manifest text without block signatures.
+         cy.createCollection(adminUser.token, {
+             name: `Test collection ${Math.floor(Math.random() * 999999)}`,
+             owner_uuid: activeUser.user.uuid,
 -            manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
++            manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n",
+         })
 -            .as('testCollection').then(function () {
++            .as("testCollection")
++            .then(function () {
+                 cy.loginAs(activeUser);
+                 cy.goToPath(`/collections/${this.testCollection.uuid}`);
+ 
 -                ['subdir', 'G%C3%BCnter\'s%20file', 'table%&?*2'].forEach((subdir) => {
 -                    cy.waitForDom().get('[data-cy=collection-files-panel]')
 -                        .contains('bar').rightclick();
 -                    cy.get('[data-cy=context-menu]')
 -                        .contains('Rename')
 -                        .click();
 -                    cy.get('[data-cy=form-dialog]')
 -                        .should('contain', 'Rename')
++                ["subdir", "G%C3%BCnter's%20file", "table%&?*2"].forEach(subdir => {
++                    cy.waitForDom().get("[data-cy=collection-files-panel]").contains("bar").rightclick();
++                    cy.get("[data-cy=context-menu]").contains("Rename").click();
++                    cy.get("[data-cy=form-dialog]")
++                        .should("contain", "Rename")
+                         .within(() => {
 -                            cy.get('input').type(`{selectall}{backspace}${subdir}/foo`);
++                            cy.get("input").type(`{selectall}{backspace}${subdir}/foo`);
+                         });
 -                    cy.get('[data-cy=form-submit-btn]').click();
 -                    cy.get('[data-cy=collection-files-panel]')
 -                        .should('not.contain', 'bar')
 -                        .and('contain', subdir);
 -                    cy.get('[data-cy=collection-files-panel]').contains(subdir).click();
++                    cy.get("[data-cy=form-submit-btn]").click();
++                    cy.get("[data-cy=collection-files-panel]").should("not.contain", "bar").and("contain", subdir);
++                    cy.get("[data-cy=collection-files-panel]").contains(subdir).click();
+ 
+                     // Rename 'subdir/foo' to 'bar'
+                     cy.wait(1000);
 -                    cy.get('[data-cy=collection-files-panel]')
 -                        .contains('foo').rightclick();
 -                    cy.get('[data-cy=context-menu]')
 -                        .contains('Rename')
 -                        .click();
 -                    cy.get('[data-cy=form-dialog]')
 -                        .should('contain', 'Rename')
++                    cy.get("[data-cy=collection-files-panel]").contains("foo").rightclick();
++                    cy.get("[data-cy=context-menu]").contains("Rename").click();
++                    cy.get("[data-cy=form-dialog]")
++                        .should("contain", "Rename")
+                         .within(() => {
 -                            cy.get('input')
 -                                .should('have.value', `${subdir}/foo`)
 -                                .type(`{selectall}{backspace}bar`);
++                            cy.get("input").should("have.value", `${subdir}/foo`).type(`{selectall}{backspace}bar`);
+                         });
 -                    cy.get('[data-cy=form-submit-btn]').click();
++                    cy.get("[data-cy=form-submit-btn]").click();
+ 
+                     // need to wait for dialog to dismiss
 -                    cy.get('[data-cy=form-dialog]').should('not.exist');
++                    cy.get("[data-cy=form-dialog]").should("not.exist");
+ 
 -                    cy.waitForDom().get('[data-cy=collection-files-panel]')
 -                        .contains('Home')
 -                        .click();
++                    cy.waitForDom().get("[data-cy=collection-files-panel]").contains("Home").click();
+ 
+                     cy.wait(2000);
 -                    cy.get('[data-cy=collection-files-panel]')
 -                        .should('contain', subdir) // empty dir kept
 -                        .and('contain', 'bar');
 -
 -                    cy.get('[data-cy=collection-files-panel]')
 -                        .contains(subdir).rightclick();
 -                    cy.get('[data-cy=context-menu]')
 -                        .contains('Remove')
 -                        .click();
 -                    cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
 -                    cy.get('[data-cy=form-dialog]').should('not.exist');
++                    cy.get("[data-cy=collection-files-panel]")
++                        .should("contain", subdir) // empty dir kept
++                        .and("contain", "bar");
++
++                    cy.get("[data-cy=collection-files-panel]").contains(subdir).rightclick();
++                    cy.get("[data-cy=context-menu]").contains("Remove").click();
++                    cy.get("[data-cy=confirmation-dialog-ok-btn]").click();
++                    cy.get("[data-cy=form-dialog]").should("not.exist");
+                 });
+             });
+     });
+ 
 -    it('shows collection owner', () => {
 +    it("shows collection owner", () => {
          cy.createCollection(adminUser.token, {
              name: `Test collection ${Math.floor(Math.random() * 999999)}`,
              owner_uuid: activeUser.user.uuid,
@@@ -738,158 -784,363 +794,352 @@@
              name: colName,
              owner_uuid: activeUser.user.uuid,
              preserve_version: true,
 -            manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo 0:3:bar\n"
 +            manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo 0:3:bar\n",
          })
 -            .as('collection').then(function () {
 +            .as("collection")
 +            .then(function () {
                  // Visit collection, check basic information
 -                cy.loginAs(activeUser)
 +                cy.loginAs(activeUser);
                  cy.goToPath(`/collections/${this.collection.uuid}`);
  
 -                cy.get('[data-cy=collection-info-panel]').should('not.contain', 'This is an old version');
 -                cy.get('[data-cy=read-only-icon]').should('not.exist');
 -                cy.get('[data-cy=collection-version-number]').should('contain', '1');
 -                cy.get('[data-cy=collection-info-panel]').should('contain', colName);
 -                cy.get('[data-cy=collection-files-panel]').should('contain', 'foo').and('contain', 'bar');
 +                cy.get("[data-cy=collection-info-panel]").should("not.contain", "This is an old version");
 +                cy.get("[data-cy=read-only-icon]").should("not.exist");
 +                cy.get("[data-cy=collection-version-number]").should("contain", "1");
 +                cy.get("[data-cy=collection-info-panel]").should("contain", colName);
 +                cy.get("[data-cy=collection-files-panel]").should("contain", "foo").and("contain", "bar");
  
                  // Modify collection, expect version number change
 -                cy.get('[data-cy=collection-files-panel]').contains('foo').rightclick();
 -                cy.get('[data-cy=context-menu]').contains('Remove').click();
 -                cy.get('[data-cy=confirmation-dialog]').should('contain', 'Removing file');
 -                cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
 -                cy.get('[data-cy=collection-version-number]').should('contain', '2');
 -                cy.get('[data-cy=collection-files-panel]').should('not.contain', 'foo').and('contain', 'bar');
 +                cy.get("[data-cy=collection-files-panel]").contains("foo").rightclick();
 +                cy.get("[data-cy=context-menu]").contains("Remove").click();
 +                cy.get("[data-cy=confirmation-dialog]").should("contain", "Removing file");
 +                cy.get("[data-cy=confirmation-dialog-ok-btn]").click();
 +                cy.get("[data-cy=collection-version-number]").should("contain", "2");
 +                cy.get("[data-cy=collection-files-panel]").should("not.contain", "foo").and("contain", "bar");
  
                  // Click on version number, check version browser. Click on past version.
 -                cy.get('[data-cy=collection-version-browser]').should('not.exist');
 -                cy.get('[data-cy=collection-version-number]').contains('2').click();
 -                cy.get('[data-cy=collection-version-browser]')
 -                    .should('contain', 'Nr').and('contain', 'Size').and('contain', 'Date')
 +                cy.get("[data-cy=collection-version-browser]").should("not.exist");
 +                cy.get("[data-cy=collection-version-number]").contains("2").click();
 +                cy.get("[data-cy=collection-version-browser]")
 +                    .should("contain", "Nr")
 +                    .and("contain", "Size")
 +                    .and("contain", "Date")
                      .within(() => {
                          // Version 1: 6 bytes in size
 -                        cy.get('[data-cy=collection-version-browser-select-1]')
 -                            .should('contain', '1')
 -                            .and('contain', '6 B')
 -                            .and('contain', adminUser.user.full_name);
 +                        cy.get("[data-cy=collection-version-browser-select-1]")
 +                            .should("contain", "1")
 +                            .and("contain", "6 B")
 +                            .and("contain", adminUser.user.full_name);
                          // Version 2: 3 bytes in size (one file removed)
 -                        cy.get('[data-cy=collection-version-browser-select-2]')
 -                            .should('contain', '2')
 -                            .and('contain', '3 B')
 -                            .and('contain', activeUser.user.full_name);
 -                        cy.get('[data-cy=collection-version-browser-select-3]')
 -                            .should('not.exist');
 -                        cy.get('[data-cy=collection-version-browser-select-1]')
 -                            .click();
 +                        cy.get("[data-cy=collection-version-browser-select-2]")
 +                            .should("contain", "2")
 +                            .and("contain", "3 B")
 +                            .and("contain", activeUser.user.full_name);
 +                        cy.get("[data-cy=collection-version-browser-select-3]").should("not.exist");
 +                        cy.get("[data-cy=collection-version-browser-select-1]").click();
                      });
 -                cy.get('[data-cy=collection-info-panel]').should('contain', 'This is an old version');
 -                cy.get('[data-cy=read-only-icon]').should('exist');
 -                cy.get('[data-cy=collection-version-number]').should('contain', '1');
 -                cy.get('[data-cy=collection-info-panel]').should('contain', colName);
 -                cy.get('[data-cy=collection-files-panel]')
 -                    .should('contain', 'foo').and('contain', 'bar');
 +                cy.get("[data-cy=collection-info-panel]").should("contain", "This is an old version");
 +                cy.get("[data-cy=read-only-icon]").should("exist");
 +                cy.get("[data-cy=collection-version-number]").should("contain", "1");
 +                cy.get("[data-cy=collection-info-panel]").should("contain", colName);
 +                cy.get("[data-cy=collection-files-panel]").should("contain", "foo").and("contain", "bar");
  
                  // Check that only old collection action are available on context menu
 -                cy.get('[data-cy=collection-panel-options-btn]').click();
 -                cy.get('[data-cy=context-menu]')
 -                    .should('contain', 'Restore version')
 -                    .and('not.contain', 'Add to favorites');
 -                cy.get('body').click(); // Collapse the menu avoiding details panel expansion
 +                cy.get("[data-cy=collection-panel-options-btn]").click();
 +                cy.get("[data-cy=context-menu]").should("contain", "Restore version").and("not.contain", "Add to favorites");
 +                cy.get("body").click(); // Collapse the menu avoiding details panel expansion
  
                  // Click on "head version" link, confirm that it's the latest version.
 -                cy.get('[data-cy=collection-info-panel]').contains('head version').click();
 -                cy.get('[data-cy=collection-info-panel]')
 -                    .should('not.contain', 'This is an old version');
 -                cy.get('[data-cy=read-only-icon]').should('not.exist');
 -                cy.get('[data-cy=collection-version-number]').should('contain', '2');
 -                cy.get('[data-cy=collection-info-panel]').should('contain', colName);
 -                cy.get('[data-cy=collection-files-panel]').
 -                    should('not.contain', 'foo').and('contain', 'bar');
 +                cy.get("[data-cy=collection-info-panel]").contains("head version").click();
 +                cy.get("[data-cy=collection-info-panel]").should("not.contain", "This is an old version");
 +                cy.get("[data-cy=read-only-icon]").should("not.exist");
 +                cy.get("[data-cy=collection-version-number]").should("contain", "2");
 +                cy.get("[data-cy=collection-info-panel]").should("contain", colName);
 +                cy.get("[data-cy=collection-files-panel]").should("not.contain", "foo").and("contain", "bar");
  
                  // Check that old collection action isn't available on context menu
 -                cy.get('[data-cy=collection-panel-options-btn]').click()
 -                cy.get('[data-cy=context-menu]').should('not.contain', 'Restore version')
 -                cy.get('body').click(); // Collapse the menu avoiding details panel expansion
 +                cy.get("[data-cy=collection-panel-options-btn]").click();
 +                cy.get("[data-cy=context-menu]").should("not.contain", "Restore version");
 +                cy.get("body").click(); // Collapse the menu avoiding details panel expansion
  
                  // Make another change, confirm new version.
 -                cy.get('[data-cy=collection-panel-options-btn]').click();
 -                cy.get('[data-cy=context-menu]').contains('Edit collection').click();
 -                cy.get('[data-cy=form-dialog]')
 -                    .should('contain', 'Edit Collection')
 +                cy.get("[data-cy=collection-panel-options-btn]").click();
 +                cy.get("[data-cy=context-menu]").contains("Edit collection").click();
 +                cy.get("[data-cy=form-dialog]")
 +                    .should("contain", "Edit Collection")
                      .within(() => {
                          // appends some text
 -                        cy.get('input').first().type(' renamed');
 +                        cy.get("input").first().type(" renamed");
                      });
 -                cy.get('[data-cy=form-submit-btn]').click();
 -                cy.get('[data-cy=collection-info-panel]')
 -                    .should('not.contain', 'This is an old version');
 -                cy.get('[data-cy=read-only-icon]').should('not.exist');
 -                cy.get('[data-cy=collection-version-number]').should('contain', '3');
 -                cy.get('[data-cy=collection-info-panel]').should('contain', colName + ' renamed');
 -                cy.get('[data-cy=collection-files-panel]')
 -                    .should('not.contain', 'foo').and('contain', 'bar');
 -                cy.get('[data-cy=collection-version-browser-select-3]')
 -                    .should('contain', '3').and('contain', '3 B');
 +                cy.get("[data-cy=form-submit-btn]").click();
 +                cy.get("[data-cy=collection-info-panel]").should("not.contain", "This is an old version");
 +                cy.get("[data-cy=read-only-icon]").should("not.exist");
 +                cy.get("[data-cy=collection-version-number]").should("contain", "3");
 +                cy.get("[data-cy=collection-info-panel]").should("contain", colName + " renamed");
 +                cy.get("[data-cy=collection-files-panel]").should("not.contain", "foo").and("contain", "bar");
 +                cy.get("[data-cy=collection-version-browser-select-3]").should("contain", "3").and("contain", "3 B");
  
                  // Check context menus on version browser
-                 cy.get("[data-cy=collection-version-browser-select-3]").rightclick({ force: true });
+                 cy.waitForDom();
 -                cy.get('[data-cy=collection-version-browser-select-3]').rightclick()
 -                cy.get('[data-cy=context-menu]')
 -                    .should('contain', 'Add to favorites')
 -                    .and('contain', 'Make a copy')
 -                    .and('contain', 'Edit collection');
 -                cy.get('body').click();
++                cy.get("[data-cy=collection-version-browser-select-3]").rightclick();
 +                cy.get("[data-cy=context-menu]")
 +                    .should("contain", "Add to favorites")
 +                    .and("contain", "Make a copy")
 +                    .and("contain", "Edit collection");
 +                cy.get("body").click();
                  // (and now an old version...)
 -                cy.get('[data-cy=collection-version-browser-select-1]').rightclick()
 -                cy.get('[data-cy=context-menu]')
 -                    .should('not.contain', 'Add to favorites')
 -                    .and('contain', 'Make a copy')
 -                    .and('not.contain', 'Edit collection');
 -                cy.get('body').click();
 +                cy.get("[data-cy=collection-version-browser-select-1]").rightclick();
 +                cy.get("[data-cy=context-menu]")
 +                    .should("not.contain", "Add to favorites")
 +                    .and("contain", "Make a copy")
 +                    .and("not.contain", "Edit collection");
 +                cy.get("body").click();
  
                  // Restore first version
 -                cy.get('[data-cy=collection-version-browser]').within(() => {
 -                    cy.get('[data-cy=collection-version-browser-select-1]').click();
 +                cy.get("[data-cy=collection-version-browser]").within(() => {
 +                    cy.get("[data-cy=collection-version-browser-select-1]").click();
                  });
 -                cy.get('[data-cy=collection-panel-options-btn]').click()
 -                cy.get('[data-cy=context-menu]').contains('Restore version').click();
 -                cy.get('[data-cy=confirmation-dialog]').should('contain', 'Restore version');
 -                cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
 -                cy.get('[data-cy=collection-info-panel]')
 -                    .should('not.contain', 'This is an old version');
 -                cy.get('[data-cy=collection-version-number]').should('contain', '4');
 -                cy.get('[data-cy=collection-info-panel]').should('contain', colName);
 -                cy.get('[data-cy=collection-files-panel]')
 -                    .should('contain', 'foo').and('contain', 'bar');
 +                cy.get("[data-cy=collection-panel-options-btn]").click();
 +                cy.get("[data-cy=context-menu]").contains("Restore version").click();
 +                cy.get("[data-cy=confirmation-dialog]").should("contain", "Restore version");
 +                cy.get("[data-cy=confirmation-dialog-ok-btn]").click();
 +                cy.get("[data-cy=collection-info-panel]").should("not.contain", "This is an old version");
 +                cy.get("[data-cy=collection-version-number]").should("contain", "4");
 +                cy.get("[data-cy=collection-info-panel]").should("contain", colName);
 +                cy.get("[data-cy=collection-files-panel]").should("contain", "foo").and("contain", "bar");
              });
      });
  
-     it("creates collection from selected files of another collection", () => {
 -    it('copies selected files into new collection', () => {
++    it("copies selected files into new collection", () => {
+         cy.createCollection(adminUser.token, {
+             name: `Test Collection ${Math.floor(Math.random() * 999999)}`,
+             owner_uuid: activeUser.user.uuid,
+             preserve_version: true,
 -            manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo 0:3:bar\n"
++            manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo 0:3:bar\n",
+         })
 -            .as('collection').then(function () {
++            .as("collection")
++            .then(function () {
+                 // Visit collection, check basic information
 -                cy.loginAs(activeUser)
++                cy.loginAs(activeUser);
+                 cy.goToPath(`/collections/${this.collection.uuid}`);
+ 
 -                cy.get('[data-cy=collection-files-panel]').within(() => {
 -                    cy.get('input[type=checkbox]').first().click();
++                cy.get("[data-cy=collection-files-panel]").within(() => {
++                    cy.get("input[type=checkbox]").first().click();
+                 });
+ 
 -                cy.get('[data-cy=collection-files-panel-options-btn]').click();
 -                cy.get('[data-cy=context-menu]').contains('Copy selected into new collection').click();
++                cy.get("[data-cy=collection-files-panel-options-btn]").click();
++                cy.get("[data-cy=context-menu]").contains("Copy selected into new collection").click();
+ 
 -                cy.get('[data-cy=form-dialog]').contains('Projects').click();
++                cy.get("[data-cy=form-dialog]").contains("Projects").click();
+ 
 -                cy.get('[data-cy=form-submit-btn]').click();
++                cy.get("[data-cy=form-submit-btn]").click();
+ 
 -                cy.waitForDom().get('.layout-pane-primary', { timeout: 12000 }).contains('Projects').click();
++                cy.waitForDom().get(".layout-pane-primary", { timeout: 12000 }).contains("Projects").click();
+ 
 -                cy.waitForDom().get('main').contains(`Files extracted from: ${this.collection.name}`).click();
 -                cy.get('[data-cy=collection-files-panel]')
 -                        .and('contain', 'bar');
++                cy.waitForDom().get("main").contains(`Files extracted from: ${this.collection.name}`).click();
++                cy.get("[data-cy=collection-files-panel]").and("contain", "bar");
+             });
+     });
+ 
 -    it('copies selected files into existing collection', () => {
++    it("copies selected files into existing collection", () => {
+         cy.createCollection(adminUser.token, {
+             name: `Test Collection ${Math.floor(Math.random() * 999999)}`,
+             owner_uuid: activeUser.user.uuid,
+             preserve_version: true,
 -            manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo 0:3:bar\n"
 -        }).as('sourceCollection')
++            manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo 0:3:bar\n",
++        }).as("sourceCollection");
+ 
+         cy.createCollection(adminUser.token, {
+             name: `Destination Collection ${Math.floor(Math.random() * 999999)}`,
+             owner_uuid: activeUser.user.uuid,
+             preserve_version: true,
 -            manifest_text: ""
 -        }).as('destinationCollection');
++            manifest_text: "",
++        }).as("destinationCollection");
+ 
 -        cy.getAll('@sourceCollection', '@destinationCollection').then(function ([sourceCollection, destinationCollection]) {
 -                // Visit collection, check basic information
 -                cy.loginAs(activeUser)
 -                cy.goToPath(`/collections/${sourceCollection.uuid}`);
++        cy.getAll("@sourceCollection", "@destinationCollection").then(function ([sourceCollection, destinationCollection]) {
++            // Visit collection, check basic information
++            cy.loginAs(activeUser);
++            cy.goToPath(`/collections/${sourceCollection.uuid}`);
+ 
 -                cy.get('[data-cy=collection-files-panel]').within(() => {
 -                    cy.get('input[type=checkbox]').first().click();
 -                });
++            cy.get("[data-cy=collection-files-panel]").within(() => {
++                cy.get("input[type=checkbox]").first().click();
++            });
+ 
 -                cy.get('[data-cy=collection-files-panel-options-btn]').click();
 -                cy.get('[data-cy=context-menu]').contains('Copy selected into existing collection').click();
++            cy.get("[data-cy=collection-files-panel-options-btn]").click();
++            cy.get("[data-cy=context-menu]").contains("Copy selected into existing collection").click();
+ 
 -                cy.get('[data-cy=form-dialog]').contains(destinationCollection.name).click();
++            cy.get("[data-cy=form-dialog]").contains(destinationCollection.name).click();
+ 
 -                cy.get('[data-cy=form-submit-btn]').click();
 -                cy.wait(2000);
++            cy.get("[data-cy=form-submit-btn]").click();
++            cy.wait(2000);
+ 
 -                cy.goToPath(`/collections/${destinationCollection.uuid}`);
++            cy.goToPath(`/collections/${destinationCollection.uuid}`);
+ 
 -                cy.get('main').contains(destinationCollection.name).should('exist');
 -                cy.get('[data-cy=collection-files-panel]')
 -                        .and('contain', 'bar');
 -            });
++            cy.get("main").contains(destinationCollection.name).should("exist");
++            cy.get("[data-cy=collection-files-panel]").and("contain", "bar");
++        });
+     });
+ 
 -    it('copies selected files into separate collections', () => {
++    it("copies selected files into separate collections", () => {
+         cy.createCollection(adminUser.token, {
+             name: `Test Collection ${Math.floor(Math.random() * 999999)}`,
+             owner_uuid: activeUser.user.uuid,
+             preserve_version: true,
 -            manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo 0:3:bar\n"
 -        }).as('sourceCollection')
++            manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo 0:3:bar\n",
++        }).as("sourceCollection");
+ 
 -        cy.getAll('@sourceCollection').then(function ([sourceCollection]) {
 -                // Visit collection, check basic information
 -                cy.loginAs(activeUser)
 -                cy.goToPath(`/collections/${sourceCollection.uuid}`);
++        cy.getAll("@sourceCollection").then(function ([sourceCollection]) {
++            // Visit collection, check basic information
++            cy.loginAs(activeUser);
++            cy.goToPath(`/collections/${sourceCollection.uuid}`);
+ 
 -                // Select both files
 -                cy.waitForDom().get('[data-cy=collection-files-panel]').within(() => {
 -                    cy.get('input[type=checkbox]').first().click();
 -                    cy.get('input[type=checkbox]').last().click();
++            // Select both files
++            cy.waitForDom()
++                .get("[data-cy=collection-files-panel]")
++                .within(() => {
++                    cy.get("input[type=checkbox]").first().click();
++                    cy.get("input[type=checkbox]").last().click();
+                 });
+ 
 -                // Copy to separate collections
 -                cy.get('[data-cy=collection-files-panel-options-btn]').click();
 -                cy.get('[data-cy=context-menu]').contains('Copy selected into separate collections').click();
 -                cy.get('[data-cy=form-dialog]').contains('Projects').click();
 -                cy.get('[data-cy=form-submit-btn]').click();
 -
 -                // Verify created collections
 -                cy.waitForDom().get('.layout-pane-primary', { timeout: 12000 }).contains('Projects').click();
 -                cy.get('main').contains(`File copied from collection ${sourceCollection.name}/foo`).click();
 -                cy.get('[data-cy=collection-files-panel]')
 -                        .and('contain', 'foo');
 -                cy.get('.layout-pane-primary').contains('Projects').click();
 -                cy.get('main').contains(`File copied from collection ${sourceCollection.name}/bar`).click();
 -                cy.get('[data-cy=collection-files-panel]')
 -                        .and('contain', 'bar');
 -
 -                // Verify separate collection menu items not present when single file selected
 -                // Wait for dom for collection to re-render
 -                cy.waitForDom().get('[data-cy=collection-files-panel]').within(() => {
 -                    cy.get('input[type=checkbox]').first().click();
++            // Copy to separate collections
++            cy.get("[data-cy=collection-files-panel-options-btn]").click();
++            cy.get("[data-cy=context-menu]").contains("Copy selected into separate collections").click();
++            cy.get("[data-cy=form-dialog]").contains("Projects").click();
++            cy.get("[data-cy=form-submit-btn]").click();
++
++            // Verify created collections
++            cy.waitForDom().get(".layout-pane-primary", { timeout: 12000 }).contains("Projects").click();
++            cy.get("main").contains(`File copied from collection ${sourceCollection.name}/foo`).click();
++            cy.get("[data-cy=collection-files-panel]").and("contain", "foo");
++            cy.get(".layout-pane-primary").contains("Projects").click();
++            cy.get("main").contains(`File copied from collection ${sourceCollection.name}/bar`).click();
++            cy.get("[data-cy=collection-files-panel]").and("contain", "bar");
++
++            // Verify separate collection menu items not present when single file selected
++            // Wait for dom for collection to re-render
++            cy.waitForDom()
++                .get("[data-cy=collection-files-panel]")
++                .within(() => {
++                    cy.get("input[type=checkbox]").first().click();
+                 });
 -                cy.get('[data-cy=collection-files-panel-options-btn]').click();
 -                cy.get('[data-cy=context-menu]').should('not.contain', 'Copy selected into separate collections');
 -                cy.get('[data-cy=context-menu]').should('not.contain', 'Move selected into separate collections');
 -            });
++            cy.get("[data-cy=collection-files-panel-options-btn]").click();
++            cy.get("[data-cy=context-menu]").should("not.contain", "Copy selected into separate collections");
++            cy.get("[data-cy=context-menu]").should("not.contain", "Move selected into separate collections");
++        });
+     });
+ 
 -    it('moves selected files into new collection', () => {
++    it("moves selected files into new collection", () => {
          cy.createCollection(adminUser.token, {
              name: `Test Collection ${Math.floor(Math.random() * 999999)}`,
              owner_uuid: activeUser.user.uuid,
              preserve_version: true,
 -            manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo 0:3:bar\n"
 +            manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo 0:3:bar\n",
          })
 -            .as('collection').then(function () {
 +            .as("collection")
 +            .then(function () {
                  // Visit collection, check basic information
 -                cy.loginAs(activeUser)
 +                cy.loginAs(activeUser);
                  cy.goToPath(`/collections/${this.collection.uuid}`);
  
 -                cy.get('[data-cy=collection-files-panel]').within(() => {
 -                    cy.get('input[type=checkbox]').first().click();
 +                cy.get("[data-cy=collection-files-panel]").within(() => {
 +                    cy.get("input[type=checkbox]").first().click();
                  });
  
 -                cy.get('[data-cy=collection-files-panel-options-btn]').click();
 -                cy.get('[data-cy=context-menu]').contains('Move selected into new collection').click();
 +                cy.get("[data-cy=collection-files-panel-options-btn]").click();
-                 cy.get("[data-cy=context-menu]").contains("Create a new collection with selected").click();
++                cy.get("[data-cy=context-menu]").contains("Move selected into new collection").click();
  
 -                cy.get('[data-cy=form-dialog]').contains('Projects').click();
 +                cy.get("[data-cy=form-dialog]").contains("Projects").click();
  
 -                cy.get('[data-cy=form-submit-btn]').click();
 +                cy.get("[data-cy=form-submit-btn]").click();
  
 -                cy.waitForDom().get('.layout-pane-primary', { timeout: 12000 }).contains('Projects').click();
 +                cy.waitForDom().get(".layout-pane-primary", { timeout: 12000 }).contains("Projects").click();
  
-                 cy.get("main").contains(`Files extracted from: ${this.collection.name}`).should("exist");
 -                cy.get('main').contains(`Files moved from: ${this.collection.name}`).click();
 -                cy.get('[data-cy=collection-files-panel]')
 -                        .and('contain', 'bar');
++                cy.get("main").contains(`Files moved from: ${this.collection.name}`).click();
++                cy.get("[data-cy=collection-files-panel]").and("contain", "bar");
+             });
+     });
+ 
 -    it('moves selected files into existing collection', () => {
++    it("moves selected files into existing collection", () => {
+         cy.createCollection(adminUser.token, {
+             name: `Test Collection ${Math.floor(Math.random() * 999999)}`,
+             owner_uuid: activeUser.user.uuid,
+             preserve_version: true,
 -            manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo 0:3:bar\n"
 -        }).as('sourceCollection')
++            manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo 0:3:bar\n",
++        }).as("sourceCollection");
+ 
+         cy.createCollection(adminUser.token, {
+             name: `Destination Collection ${Math.floor(Math.random() * 999999)}`,
+             owner_uuid: activeUser.user.uuid,
+             preserve_version: true,
 -            manifest_text: ""
 -        }).as('destinationCollection');
++            manifest_text: "",
++        }).as("destinationCollection");
+ 
 -        cy.getAll('@sourceCollection', '@destinationCollection').then(function ([sourceCollection, destinationCollection]) {
 -                // Visit collection, check basic information
 -                cy.loginAs(activeUser)
 -                cy.goToPath(`/collections/${sourceCollection.uuid}`);
++        cy.getAll("@sourceCollection", "@destinationCollection").then(function ([sourceCollection, destinationCollection]) {
++            // Visit collection, check basic information
++            cy.loginAs(activeUser);
++            cy.goToPath(`/collections/${sourceCollection.uuid}`);
+ 
 -                cy.get('[data-cy=collection-files-panel]').within(() => {
 -                    cy.get('input[type=checkbox]').first().click();
 -                });
++            cy.get("[data-cy=collection-files-panel]").within(() => {
++                cy.get("input[type=checkbox]").first().click();
++            });
+ 
 -                cy.get('[data-cy=collection-files-panel-options-btn]').click();
 -                cy.get('[data-cy=context-menu]').contains('Move selected into existing collection').click();
++            cy.get("[data-cy=collection-files-panel-options-btn]").click();
++            cy.get("[data-cy=context-menu]").contains("Move selected into existing collection").click();
+ 
 -                cy.get('[data-cy=form-dialog]').contains(destinationCollection.name).click();
++            cy.get("[data-cy=form-dialog]").contains(destinationCollection.name).click();
+ 
 -                cy.get('[data-cy=form-submit-btn]').click();
 -                cy.wait(2000);
++            cy.get("[data-cy=form-submit-btn]").click();
++            cy.wait(2000);
+ 
 -                cy.goToPath(`/collections/${destinationCollection.uuid}`);
++            cy.goToPath(`/collections/${destinationCollection.uuid}`);
+ 
 -                cy.get('main').contains(destinationCollection.name).should('exist');
 -                cy.get('[data-cy=collection-files-panel]')
 -                        .and('contain', 'bar');
 -            });
++            cy.get("main").contains(destinationCollection.name).should("exist");
++            cy.get("[data-cy=collection-files-panel]").and("contain", "bar");
++        });
+     });
+ 
 -    it('moves selected files into separate collections', () => {
++    it("moves selected files into separate collections", () => {
+         cy.createCollection(adminUser.token, {
+             name: `Test Collection ${Math.floor(Math.random() * 999999)}`,
+             owner_uuid: activeUser.user.uuid,
+             preserve_version: true,
 -            manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo 0:3:bar\n"
 -        }).as('sourceCollection')
++            manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo 0:3:bar\n",
++        }).as("sourceCollection");
+ 
 -        cy.getAll('@sourceCollection').then(function ([sourceCollection]) {
 -                // Visit collection, check basic information
 -                cy.loginAs(activeUser)
 -                cy.goToPath(`/collections/${sourceCollection.uuid}`);
 -
 -                // Select both files
 -                cy.get('[data-cy=collection-files-panel]').within(() => {
 -                    cy.get('input[type=checkbox]').first().click();
 -                    cy.get('input[type=checkbox]').last().click();
 -                });
++        cy.getAll("@sourceCollection").then(function ([sourceCollection]) {
++            // Visit collection, check basic information
++            cy.loginAs(activeUser);
++            cy.goToPath(`/collections/${sourceCollection.uuid}`);
+ 
 -                // Copy to separate collections
 -                cy.get('[data-cy=collection-files-panel-options-btn]').click();
 -                cy.get('[data-cy=context-menu]').contains('Move selected into separate collections').click();
 -                cy.get('[data-cy=form-dialog]').contains('Projects').click();
 -                cy.get('[data-cy=form-submit-btn]').click();
 -
 -                // Verify created collections
 -                cy.waitForDom().get('.layout-pane-primary', { timeout: 12000 }).contains('Projects').click();
 -                cy.get('main').contains(`File moved from collection ${sourceCollection.name}/foo`).click();
 -                cy.get('[data-cy=collection-files-panel]')
 -                        .and('contain', 'foo');
 -                cy.get('.layout-pane-primary').contains('Projects').click();
 -                cy.get('main').contains(`File moved from collection ${sourceCollection.name}/bar`).click();
 -                cy.get('[data-cy=collection-files-panel]')
 -                        .and('contain', 'bar');
++            // Select both files
++            cy.get("[data-cy=collection-files-panel]").within(() => {
++                cy.get("input[type=checkbox]").first().click();
++                cy.get("input[type=checkbox]").last().click();
              });
++
++            // Copy to separate collections
++            cy.get("[data-cy=collection-files-panel-options-btn]").click();
++            cy.get("[data-cy=context-menu]").contains("Move selected into separate collections").click();
++            cy.get("[data-cy=form-dialog]").contains("Projects").click();
++            cy.get("[data-cy=form-submit-btn]").click();
++
++            // Verify created collections
++            cy.waitForDom().get(".layout-pane-primary", { timeout: 12000 }).contains("Projects").click();
++            cy.get("main").contains(`File moved from collection ${sourceCollection.name}/foo`).click();
++            cy.get("[data-cy=collection-files-panel]").and("contain", "foo");
++            cy.get(".layout-pane-primary").contains("Projects").click();
++            cy.get("main").contains(`File moved from collection ${sourceCollection.name}/bar`).click();
++            cy.get("[data-cy=collection-files-panel]").and("contain", "bar");
++        });
      });
  
 -    it('creates new collection with properties on home project', function () {
 +    it("creates new collection with properties on home project", function () {
          cy.loginAs(activeUser);
          cy.goToPath(`/projects/${activeUser.user.uuid}`);
 -        cy.get('[data-cy=breadcrumb-first]').should('contain', 'Projects');
 -        cy.get('[data-cy=breadcrumb-last]').should('not.exist');
 +        cy.get("[data-cy=breadcrumb-first]").should("contain", "Projects");
 +        cy.get("[data-cy=breadcrumb-last]").should("not.exist");
          // Create new collection
 -        cy.get('[data-cy=side-panel-button]').click();
 -        cy.get('[data-cy=side-panel-new-collection]').click();
 +        cy.get("[data-cy=side-panel-button]").click();
 +        cy.get("[data-cy=side-panel-new-collection]").click();
          // Name between brackets tests bugfix #17582
          const collName = `[Test collection (${Math.floor(999999 * Math.random())})]`;
  
diff --cc cypress/integration/process.spec.js
index 4424b035,64f27c50..438acbf1
--- a/cypress/integration/process.spec.js
+++ b/cypress/integration/process.spec.js
@@@ -2,7 -2,9 +2,9 @@@
  //
  // SPDX-License-Identifier: AGPL-3.0
  
 -import { ContainerState } from 'models/container';
++import { ContainerState } from "models/container";
+ 
 -describe('Process tests', function() {
 +describe("Process tests", function () {
      let activeUser;
      let adminUser;
  
@@@ -88,839 -86,1345 +90,1268 @@@
          });
      }
  
-     it("shows process logs", function () {
-         const crName = "test_container_request";
-         createContainerRequest(activeUser, crName, "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (containerRequest) {
-             cy.loginAs(activeUser);
-             cy.goToPath(`/processes/${containerRequest.uuid}`);
-             cy.get("[data-cy=process-details]").should("contain", crName);
-             cy.get("[data-cy=process-logs]").should("contain", "No logs yet").and("not.contain", "hello world");
-             cy.createLog(activeUser.token, {
-                 object_uuid: containerRequest.container_uuid,
-                 properties: {
-                     text: "hello world",
-                 },
-                 event_type: "stdout",
-             }).then(function (log) {
-                 cy.get("[data-cy=process-logs]", { timeout: 7000 }).should("not.contain", "No logs yet").and("contain", "hello world");
 -    describe('Details panel', function() {
 -        it('shows process details', function() {
++    describe("Details panel", function () {
++        it("shows process details", function () {
+             createContainerRequest(
+                 activeUser,
+                 `test_container_request ${Math.floor(Math.random() * 999999)}`,
 -                'arvados/jobs',
 -                ['echo', 'hello world'],
 -                false, 'Committed')
 -            .then(function(containerRequest) {
++                "arvados/jobs",
++                ["echo", "hello world"],
++                false,
++                "Committed"
++            ).then(function (containerRequest) {
+                 cy.loginAs(activeUser);
+                 cy.goToPath(`/processes/${containerRequest.uuid}`);
 -                cy.get('[data-cy=process-details]').should('contain', containerRequest.name);
 -                cy.get('[data-cy=process-details-attributes-modifiedby-user]').contains(`Active User (${activeUser.user.uuid})`);
 -                cy.get('[data-cy=process-details-attributes-runtime-user]').should('not.exist');
++                cy.get("[data-cy=process-details]").should("contain", containerRequest.name);
++                cy.get("[data-cy=process-details-attributes-modifiedby-user]").contains(`Active User (${activeUser.user.uuid})`);
++                cy.get("[data-cy=process-details-attributes-runtime-user]").should("not.exist");
+             });
+ 
+             // Fake submitted by another user
 -            cy.intercept({method: 'GET', url: '**/arvados/v1/container_requests/*'}, (req) => {
 -                req.reply((res) => {
 -                    res.body.modified_by_user_uuid = 'zzzzz-tpzed-000000000000000';
++            cy.intercept({ method: "GET", url: "**/arvados/v1/container_requests/*" }, req => {
++                req.reply(res => {
++                    res.body.modified_by_user_uuid = "zzzzz-tpzed-000000000000000";
+                 });
+             });
+ 
+             createContainerRequest(
+                 activeUser,
+                 `test_container_request ${Math.floor(Math.random() * 999999)}`,
 -                'arvados/jobs',
 -                ['echo', 'hello world'],
 -                false, 'Committed')
 -            .then(function(containerRequest) {
++                "arvados/jobs",
++                ["echo", "hello world"],
++                false,
++                "Committed"
++            ).then(function (containerRequest) {
+                 cy.loginAs(activeUser);
+                 cy.goToPath(`/processes/${containerRequest.uuid}`);
 -                cy.get('[data-cy=process-details]').should('contain', containerRequest.name);
 -                cy.get('[data-cy=process-details-attributes-modifiedby-user]').contains(`zzzzz-tpzed-000000000000000`);
 -                cy.get('[data-cy=process-details-attributes-runtime-user]').contains(`Active User (${activeUser.user.uuid})`);
++                cy.get("[data-cy=process-details]").should("contain", containerRequest.name);
++                cy.get("[data-cy=process-details-attributes-modifiedby-user]").contains(`zzzzz-tpzed-000000000000000`);
++                cy.get("[data-cy=process-details-attributes-runtime-user]").contains(`Active User (${activeUser.user.uuid})`);
              });
          });
-     });
  
-     it("shows process details", function () {
-         createContainerRequest(
-             activeUser,
-             `test_container_request ${Math.floor(Math.random() * 999999)}`,
-             "arvados/jobs",
-             ["echo", "hello world"],
-             false,
-             "Committed"
-         ).then(function (containerRequest) {
-             cy.loginAs(activeUser);
-             cy.goToPath(`/processes/${containerRequest.uuid}`);
-             cy.get("[data-cy=process-details]").should("contain", containerRequest.name);
-             cy.get("[data-cy=process-details-attributes-modifiedby-user]").contains(`Active User (${activeUser.user.uuid})`);
-             cy.get("[data-cy=process-details-attributes-runtime-user]").should("not.exist");
 -        it('should show runtime status indicators', function() {
++        it("should show runtime status indicators", function () {
+             // Setup running container with runtime_status error & warning messages
 -            createContainerRequest(
 -                activeUser,
 -                'test_container_request',
 -                'arvados/jobs',
 -                ['echo', 'hello world'],
 -                false, 'Committed')
 -            .as('containerRequest')
 -            .then(function(containerRequest) {
 -                expect(containerRequest.state).to.equal('Committed');
 -                expect(containerRequest.container_uuid).not.to.be.equal('');
 -
 -                cy.getContainer(activeUser.token, containerRequest.container_uuid)
 -                .then(function(queuedContainer) {
 -                    expect(queuedContainer.state).to.be.equal('Queued');
 -                });
 -                cy.updateContainer(adminUser.token, containerRequest.container_uuid, {
 -                    state: 'Locked'
 -                }).then(function(lockedContainer) {
 -                    expect(lockedContainer.state).to.be.equal('Locked');
 -
 -                    cy.updateContainer(adminUser.token, lockedContainer.uuid, {
 -                        state: 'Running',
 -                        runtime_status: {
 -                            error: 'Something went wrong',
 -                            errorDetail: 'Process exited with status 1',
 -                            warning: 'Free disk space is low',
 -                        }
 -                    })
 -                    .as('runningContainer')
 -                    .then(function(runningContainer) {
 -                        expect(runningContainer.state).to.be.equal('Running');
 -                        expect(runningContainer.runtime_status).to.be.deep.equal({
 -                            'error': 'Something went wrong',
 -                            'errorDetail': 'Process exited with status 1',
 -                            'warning': 'Free disk space is low',
 -                        });
++            createContainerRequest(activeUser, "test_container_request", "arvados/jobs", ["echo", "hello world"], false, "Committed")
++                .as("containerRequest")
++                .then(function (containerRequest) {
++                    expect(containerRequest.state).to.equal("Committed");
++                    expect(containerRequest.container_uuid).not.to.be.equal("");
++
++                    cy.getContainer(activeUser.token, containerRequest.container_uuid).then(function (queuedContainer) {
++                        expect(queuedContainer.state).to.be.equal("Queued");
+                     });
 -                })
 -            });
++                    cy.updateContainer(adminUser.token, containerRequest.container_uuid, {
++                        state: "Locked",
++                    }).then(function (lockedContainer) {
++                        expect(lockedContainer.state).to.be.equal("Locked");
++
++                        cy.updateContainer(adminUser.token, lockedContainer.uuid, {
++                            state: "Running",
++                            runtime_status: {
++                                error: "Something went wrong",
++                                errorDetail: "Process exited with status 1",
++                                warning: "Free disk space is low",
++                            },
++                        })
++                            .as("runningContainer")
++                            .then(function (runningContainer) {
++                                expect(runningContainer.state).to.be.equal("Running");
++                                expect(runningContainer.runtime_status).to.be.deep.equal({
++                                    error: "Something went wrong",
++                                    errorDetail: "Process exited with status 1",
++                                    warning: "Free disk space is low",
++                                });
++                            });
++                    });
++                });
+             // Test that the UI shows the error and warning messages
 -            cy.getAll('@containerRequest', '@runningContainer').then(function([containerRequest]) {
++            cy.getAll("@containerRequest", "@runningContainer").then(function ([containerRequest]) {
+                 cy.loginAs(activeUser);
+                 cy.goToPath(`/processes/${containerRequest.uuid}`);
 -                cy.get('[data-cy=process-runtime-status-error]')
 -                    .should('contain', 'Something went wrong')
 -                    .and('contain', 'Process exited with status 1');
 -                cy.get('[data-cy=process-runtime-status-warning]')
 -                    .should('contain', 'Free disk space is low')
 -                    .and('contain', 'No additional warning details available');
++                cy.get("[data-cy=process-runtime-status-error]")
++                    .should("contain", "Something went wrong")
++                    .and("contain", "Process exited with status 1");
++                cy.get("[data-cy=process-runtime-status-warning]")
++                    .should("contain", "Free disk space is low")
++                    .and("contain", "No additional warning details available");
+             });
+ 
 -
+             // Force container_count for testing
+             let containerCount = 2;
 -            cy.intercept({method: 'GET', url: '**/arvados/v1/container_requests/*'}, (req) => {
 -                req.reply((res) => {
++            cy.intercept({ method: "GET", url: "**/arvados/v1/container_requests/*" }, req => {
++                req.reply(res => {
+                     res.body.container_count = containerCount;
+                 });
+             });
+ 
 -            cy.getAll('@containerRequest').then(function([containerRequest]) {
++            cy.getAll("@containerRequest").then(function ([containerRequest]) {
+                 cy.goToPath(`/processes/${containerRequest.uuid}`);
 -                cy.get('[data-cy=process-runtime-status-retry-warning]', {timeout: 7000})
 -                    .should('contain', 'Process retried 1 time');
++                cy.get("[data-cy=process-runtime-status-retry-warning]", { timeout: 7000 }).should("contain", "Process retried 1 time");
+             });
+ 
 -            cy.getAll('@containerRequest').then(function([containerRequest]) {
++            cy.getAll("@containerRequest").then(function ([containerRequest]) {
+                 containerCount = 3;
+                 cy.goToPath(`/processes/${containerRequest.uuid}`);
 -                cy.get('[data-cy=process-runtime-status-retry-warning]', {timeout: 7000})
 -                    .should('contain', 'Process retried 2 times');
++                cy.get("[data-cy=process-runtime-status-retry-warning]", { timeout: 7000 }).should("contain", "Process retried 2 times");
+             });
          });
  
-         // Fake submitted by another user
-         cy.intercept({ method: "GET", url: "**/arvados/v1/container_requests/*" }, req => {
-             req.reply(res => {
-                 res.body.modified_by_user_uuid = "zzzzz-tpzed-000000000000000";
 -        it('allows copying processes', function() {
 -            const crName = 'first_container_request';
 -            const copiedCrName = 'copied_container_request';
 -            createContainerRequest(
 -                activeUser,
 -                crName,
 -                'arvados/jobs',
 -                ['echo', 'hello world'],
 -                false, 'Committed')
 -            .then(function(containerRequest) {
++        it("allows copying processes", function () {
++            const crName = "first_container_request";
++            const copiedCrName = "copied_container_request";
++            createContainerRequest(activeUser, crName, "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (containerRequest) {
+                 cy.loginAs(activeUser);
+                 cy.goToPath(`/processes/${containerRequest.uuid}`);
 -                cy.get('[data-cy=process-details]').should('contain', crName);
++                cy.get("[data-cy=process-details]").should("contain", crName);
+ 
 -                cy.get('[data-cy=process-details]').find('button[title="More options"]').click();
 -                cy.get('ul[data-cy=context-menu]').contains("Copy and re-run process").click();
++                cy.get("[data-cy=process-details]").find('button[title="More options"]').click();
++                cy.get("ul[data-cy=context-menu]").contains("Copy and re-run process").click();
+             });
+ 
 -            cy.get('[data-cy=form-dialog]').within(() => {
 -                cy.get('input[name=name]').clear().type(copiedCrName);
 -                cy.get('[data-cy=projects-tree-home-tree-picker]').click();
 -                cy.get('[data-cy=form-submit-btn]').click();
++            cy.get("[data-cy=form-dialog]").within(() => {
++                cy.get("input[name=name]").clear().type(copiedCrName);
++                cy.get("[data-cy=projects-tree-home-tree-picker]").click();
++                cy.get("[data-cy=form-submit-btn]").click();
              });
+ 
 -            cy.get('[data-cy=process-details]').should('contain', copiedCrName);
 -            cy.get('[data-cy=process-details]').find('button').contains('Run');
++            cy.get("[data-cy=process-details]").should("contain", copiedCrName);
++            cy.get("[data-cy=process-details]").find("button").contains("Run");
          });
  
-         createContainerRequest(
-             activeUser,
-             `test_container_request ${Math.floor(Math.random() * 999999)}`,
-             "arvados/jobs",
-             ["echo", "hello world"],
-             false,
-             "Committed"
-         ).then(function (containerRequest) {
-             cy.loginAs(activeUser);
-             cy.goToPath(`/processes/${containerRequest.uuid}`);
-             cy.get("[data-cy=process-details]").should("contain", containerRequest.name);
-             cy.get("[data-cy=process-details-attributes-modifiedby-user]").contains(`zzzzz-tpzed-000000000000000`);
-             cy.get("[data-cy=process-details-attributes-runtime-user]").contains(`Active User (${activeUser.user.uuid})`);
 -        const getFakeContainer = (fakeContainerUuid) => ({
++        const getFakeContainer = fakeContainerUuid => ({
+             href: `/containers/${fakeContainerUuid}`,
+             kind: "arvados#container",
+             etag: "ecfosljpnxfari9a8m7e4yv06",
+             uuid: fakeContainerUuid,
+             owner_uuid: "zzzzz-tpzed-000000000000000",
+             created_at: "2023-02-13T15:55:47.308915000Z",
+             modified_by_client_uuid: "zzzzz-ozdt8-q6dzdi1lcc03155",
+             modified_by_user_uuid: "zzzzz-tpzed-000000000000000",
+             modified_at: "2023-02-15T19:12:45.987086000Z",
+             command: [
 -            "arvados-cwl-runner",
 -            "--api=containers",
 -            "--local",
 -            "--project-uuid=zzzzz-j7d0g-yr18k784zplfeza",
 -            "/var/lib/cwl/workflow.json#main",
 -            "/var/lib/cwl/cwl.input.json",
++                "arvados-cwl-runner",
++                "--api=containers",
++                "--local",
++                "--project-uuid=zzzzz-j7d0g-yr18k784zplfeza",
++                "/var/lib/cwl/workflow.json#main",
++                "/var/lib/cwl/cwl.input.json",
+             ],
+             container_image: "4ad7d11381df349e464694762db14e04+303",
+             cwd: "/var/spool/cwl",
+             environment: {},
+             exit_code: null,
+             finished_at: null,
+             locked_by_uuid: null,
+             log: null,
+             output: null,
+             output_path: "/var/spool/cwl",
+             progress: null,
+             runtime_constraints: {
 -            API: true,
 -            cuda: {
 -                device_count: 0,
 -                driver_version: "",
 -                hardware_capability: "",
 -            },
 -            keep_cache_disk: 2147483648,
 -            keep_cache_ram: 0,
 -            ram: 1342177280,
 -            vcpus: 1,
++                API: true,
++                cuda: {
++                    device_count: 0,
++                    driver_version: "",
++                    hardware_capability: "",
++                },
++                keep_cache_disk: 2147483648,
++                keep_cache_ram: 0,
++                ram: 1342177280,
++                vcpus: 1,
+             },
+             runtime_status: {},
+             started_at: null,
+             auth_uuid: null,
+             scheduling_parameters: {
 -            max_run_time: 0,
 -            partitions: [],
 -            preemptible: false,
++                max_run_time: 0,
++                partitions: [],
++                preemptible: false,
+             },
+             runtime_user_uuid: "zzzzz-tpzed-vllbpebicy84rd5",
+             runtime_auth_scopes: ["all"],
+             lock_count: 2,
+             gateway_address: null,
+             interactive_session_started: false,
+             output_storage_classes: ["default"],
+             output_properties: {},
+             cost: 0.0,
+             subrequests_cost: 0.0,
          });
-     });
  
-     it("filters process logs by event type", function () {
-         const nodeInfoLogs = [
-             "Host Information",
-             "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",
-             "CPU Information",
-             "processor  : 0",
-             "vendor_id  : GenuineIntel",
-             "cpu family : 6",
-             "model      : 79",
-             "model name : Intel(R) Xeon(R) CPU E5-2673 v4 @ 2.30GHz",
-         ];
-         const crunchRunLogs = [
-             "2022-03-22T13:56:22.542417997Z using local keepstore process (pid 3733) at http://localhost:46837, writing logs to keepstore.txt in log collection",
-             "2022-03-22T13:56:26.237571754Z crunch-run 2.4.0~dev20220321141729 (go1.17.1) started",
-             "2022-03-22T13:56:26.244704134Z crunch-run process has uid=0(root) gid=0(root) groups=0(root)",
-             "2022-03-22T13:56:26.244862836Z Executing container 'zzzzz-dz642-1wokwvcct9s9du3' using docker runtime",
-             "2022-03-22T13:56:26.245037738Z Executing on host 'compute-99cb150b26149780de44b929577e1aed-19rgca8vobuvc4p'",
-         ];
-         const stdoutLogs = [
-             "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec dui nisi, hendrerit porta sapien a, pretium dignissim purus.",
-             "Integer viverra, mauris finibus aliquet ultricies, dui mauris cursus justo, ut venenatis nibh ex eget neque.",
-             "In hac habitasse platea dictumst.",
-             "Fusce fringilla turpis id accumsan faucibus. Donec congue congue ex non posuere. In semper mi quis tristique rhoncus.",
-             "Interdum et malesuada fames ac ante ipsum primis in faucibus.",
-             "Quisque fermentum tortor ex, ut suscipit velit feugiat faucibus.",
-             "Donec vitae porta risus, at luctus nulla. Mauris gravida iaculis ipsum, id sagittis tortor egestas ac.",
-             "Maecenas condimentum volutpat nulla. Integer lacinia maximus risus eu posuere.",
-             "Donec vitae leo id augue gravida bibendum.",
-             "Nam libero libero, pretium ac faucibus elementum, mattis nec ex.",
-             "Nullam id laoreet nibh. Vivamus tellus metus, pretium quis justo ut, bibendum varius metus. Pellentesque vitae accumsan lorem, quis tincidunt augue.",
-             "Aliquam viverra nisi nulla, et efficitur dolor mattis in.",
-             "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.",
-             "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.",
-             "Phasellus non ex quis arcu tempus faucibus molestie in sapien.",
-             "Duis tristique semper dolor, vitae pulvinar risus.",
-             "Aliquam tortor elit, luctus nec tortor eget, porta tristique nulla.",
-             "Nulla eget mollis ipsum.",
-         ];
 -        it('shows cancel button when appropriate', function() {
++        it("shows cancel button when appropriate", function () {
+             // Ignore collection requests
 -            cy.intercept({method: 'GET', url: `**/arvados/v1/collections/*`}, {
 -                statusCode: 200,
 -                body: {}
 -            });
++            cy.intercept(
++                { method: "GET", url: `**/arvados/v1/collections/*` },
++                {
++                    statusCode: 200,
++                    body: {},
++                }
++            );
  
-         createContainerRequest(activeUser, "test_container_request", "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (
-             containerRequest
-         ) {
-             cy.logsForContainer(activeUser.token, containerRequest.container_uuid, "node-info", nodeInfoLogs).as("nodeInfoLogs");
-             cy.logsForContainer(activeUser.token, containerRequest.container_uuid, "crunch-run", crunchRunLogs).as("crunchRunLogs");
-             cy.logsForContainer(activeUser.token, containerRequest.container_uuid, "stdout", stdoutLogs).as("stdoutLogs");
-             cy.getAll("@stdoutLogs", "@nodeInfoLogs", "@crunchRunLogs").then(function () {
-                 cy.loginAs(activeUser);
+             // Uncommitted container
+             const crUncommitted = `Test process ${Math.floor(Math.random() * 999999)}`;
 -            createContainerRequest(
 -                activeUser,
 -                crUncommitted,
 -                'arvados/jobs',
 -                ['echo', 'hello world'],
 -                false, 'Uncommitted')
 -            .then(function(containerRequest) {
++            createContainerRequest(activeUser, crUncommitted, "arvados/jobs", ["echo", "hello world"], false, "Uncommitted").then(function (
++                containerRequest
++            ) {
+                 // Navigate to process and verify run / cancel button
                  cy.goToPath(`/processes/${containerRequest.uuid}`);
-                 // Should show main logs by default
-                 cy.get("[data-cy=process-logs-filter]", { timeout: 7000 }).should("contain", "Main logs");
-                 cy.get("[data-cy=process-logs]")
-                     .should("contain", stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
-                     .and("not.contain", nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
-                     .and("contain", crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
-                 // Select 'All logs'
-                 cy.get("[data-cy=process-logs-filter]").click();
-                 cy.get("body").contains("li", "All logs").click();
-                 cy.get("[data-cy=process-logs]")
-                     .should("contain", stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
-                     .and("contain", nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
-                     .and("contain", crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
-                 // Select 'node-info' logs
-                 cy.get("[data-cy=process-logs-filter]").click();
-                 cy.get("body").contains("li", "node-info").click();
-                 cy.get("[data-cy=process-logs]")
-                     .should("not.contain", stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
-                     .and("contain", nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
-                     .and("not.contain", crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
-                 // Select 'stdout' logs
-                 cy.get("[data-cy=process-logs-filter]").click();
-                 cy.get("body").contains("li", "stdout").click();
-                 cy.get("[data-cy=process-logs]")
-                     .should("contain", stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
-                     .and("not.contain", nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
-                     .and("not.contain", crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
+                 cy.waitForDom();
 -                cy.get('[data-cy=process-details]').should('contain', crUncommitted);
 -                cy.get('[data-cy=process-run-button]').should('exist');
 -                cy.get('[data-cy=process-cancel-button]').should('not.exist');
++                cy.get("[data-cy=process-details]").should("contain", crUncommitted);
++                cy.get("[data-cy=process-run-button]").should("exist");
++                cy.get("[data-cy=process-cancel-button]").should("not.exist");
+             });
+ 
+             // Queued container
+             const crQueued = `Test process ${Math.floor(Math.random() * 999999)}`;
 -            const fakeCrUuid = 'zzzzz-dz642-000000000000001';
 -            createContainerRequest(
 -                activeUser,
 -                crQueued,
 -                'arvados/jobs',
 -                ['echo', 'hello world'],
 -                false, 'Committed')
 -            .then(function(containerRequest) {
++            const fakeCrUuid = "zzzzz-dz642-000000000000001";
++            createContainerRequest(activeUser, crQueued, "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (
++                containerRequest
++            ) {
+                 // Fake container uuid
 -                cy.intercept({method: 'GET', url: `**/arvados/v1/container_requests/${containerRequest.uuid}`}, (req) => {
 -                    req.reply((res) => {
++                cy.intercept({ method: "GET", url: `**/arvados/v1/container_requests/${containerRequest.uuid}` }, req => {
++                    req.reply(res => {
+                         res.body.output_uuid = fakeCrUuid;
+                         res.body.priority = 500;
+                         res.body.state = "Committed";
+                     });
+                 });
+ 
+                 // Fake container
+                 const container = getFakeContainer(fakeCrUuid);
 -                cy.intercept({method: 'GET', url: `**/arvados/v1/container/${fakeCrUuid}`}, {
 -                    statusCode: 200,
 -                    body: {...container, state: "Queued", priority: 500}
 -                });
++                cy.intercept(
++                    { method: "GET", url: `**/arvados/v1/container/${fakeCrUuid}` },
++                    {
++                        statusCode: 200,
++                        body: { ...container, state: "Queued", priority: 500 },
++                    }
++                );
+ 
+                 // Navigate to process and verify cancel button
+                 cy.goToPath(`/processes/${containerRequest.uuid}`);
+                 cy.waitForDom();
 -                cy.get('[data-cy=process-details]').should('contain', crQueued);
 -                cy.get('[data-cy=process-cancel-button]').contains('Cancel');
++                cy.get("[data-cy=process-details]").should("contain", crQueued);
++                cy.get("[data-cy=process-cancel-button]").contains("Cancel");
+             });
+ 
+             // Locked container
+             const crLocked = `Test process ${Math.floor(Math.random() * 999999)}`;
 -            const fakeCrLockedUuid = 'zzzzz-dz642-000000000000002';
 -            createContainerRequest(
 -                activeUser,
 -                crLocked,
 -                'arvados/jobs',
 -                ['echo', 'hello world'],
 -                false, 'Committed')
 -            .then(function(containerRequest) {
++            const fakeCrLockedUuid = "zzzzz-dz642-000000000000002";
++            createContainerRequest(activeUser, crLocked, "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (
++                containerRequest
++            ) {
+                 // Fake container uuid
 -                cy.intercept({method: 'GET', url: `**/arvados/v1/container_requests/${containerRequest.uuid}`}, (req) => {
 -                    req.reply((res) => {
++                cy.intercept({ method: "GET", url: `**/arvados/v1/container_requests/${containerRequest.uuid}` }, req => {
++                    req.reply(res => {
+                         res.body.output_uuid = fakeCrLockedUuid;
+                         res.body.priority = 500;
+                         res.body.state = "Committed";
+                     });
+                 });
+ 
+                 // Fake container
+                 const container = getFakeContainer(fakeCrLockedUuid);
 -                cy.intercept({method: 'GET', url: `**/arvados/v1/container/${fakeCrLockedUuid}`}, {
 -                    statusCode: 200,
 -                    body: {...container, state: "Locked", priority: 500}
 -                });
++                cy.intercept(
++                    { method: "GET", url: `**/arvados/v1/container/${fakeCrLockedUuid}` },
++                    {
++                        statusCode: 200,
++                        body: { ...container, state: "Locked", priority: 500 },
++                    }
++                );
+ 
+                 // Navigate to process and verify cancel button
+                 cy.goToPath(`/processes/${containerRequest.uuid}`);
+                 cy.waitForDom();
 -                cy.get('[data-cy=process-details]').should('contain', crLocked);
 -                cy.get('[data-cy=process-cancel-button]').contains('Cancel');
++                cy.get("[data-cy=process-details]").should("contain", crLocked);
++                cy.get("[data-cy=process-cancel-button]").contains("Cancel");
+             });
+ 
+             // On Hold container
+             const crOnHold = `Test process ${Math.floor(Math.random() * 999999)}`;
 -            const fakeCrOnHoldUuid = 'zzzzz-dz642-000000000000003';
 -            createContainerRequest(
 -                activeUser,
 -                crOnHold,
 -                'arvados/jobs',
 -                ['echo', 'hello world'],
 -                false, 'Committed')
 -            .then(function(containerRequest) {
++            const fakeCrOnHoldUuid = "zzzzz-dz642-000000000000003";
++            createContainerRequest(activeUser, crOnHold, "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (
++                containerRequest
++            ) {
+                 // Fake container uuid
 -                cy.intercept({method: 'GET', url: `**/arvados/v1/container_requests/${containerRequest.uuid}`}, (req) => {
 -                    req.reply((res) => {
++                cy.intercept({ method: "GET", url: `**/arvados/v1/container_requests/${containerRequest.uuid}` }, req => {
++                    req.reply(res => {
+                         res.body.output_uuid = fakeCrOnHoldUuid;
+                         res.body.priority = 0;
+                         res.body.state = "Committed";
+                     });
+                 });
+ 
+                 // Fake container
+                 const container = getFakeContainer(fakeCrOnHoldUuid);
 -                cy.intercept({method: 'GET', url: `**/arvados/v1/container/${fakeCrOnHoldUuid}`}, {
 -                    statusCode: 200,
 -                    body: {...container, state: "Queued", priority: 0}
 -                });
++                cy.intercept(
++                    { method: "GET", url: `**/arvados/v1/container/${fakeCrOnHoldUuid}` },
++                    {
++                        statusCode: 200,
++                        body: { ...container, state: "Queued", priority: 0 },
++                    }
++                );
+ 
+                 // Navigate to process and verify cancel button
+                 cy.goToPath(`/processes/${containerRequest.uuid}`);
+                 cy.waitForDom();
 -                cy.get('[data-cy=process-details]').should('contain', crOnHold);
 -                cy.get('[data-cy=process-run-button]').should('exist');
 -                cy.get('[data-cy=process-cancel-button]').should('not.exist');
++                cy.get("[data-cy=process-details]").should("contain", crOnHold);
++                cy.get("[data-cy=process-run-button]").should("exist");
++                cy.get("[data-cy=process-cancel-button]").should("not.exist");
              });
          });
 -
      });
  
-     it("should show runtime status indicators", function () {
-         // Setup running container with runtime_status error & warning messages
-         createContainerRequest(activeUser, "test_container_request", "arvados/jobs", ["echo", "hello world"], false, "Committed")
-             .as("containerRequest")
-             .then(function (containerRequest) {
-                 expect(containerRequest.state).to.equal("Committed");
-                 expect(containerRequest.container_uuid).not.to.be.equal("");
 -
 -    describe('Logs panel', function() {
 -        it('shows live process logs', function() {
 -            cy.intercept({method: 'GET', url: '**/arvados/v1/containers/*'}, (req) => {
 -                req.reply((res) => {
++    describe("Logs panel", function () {
++        it("shows live process logs", function () {
++            cy.intercept({ method: "GET", url: "**/arvados/v1/containers/*" }, req => {
++                req.reply(res => {
+                     res.body.state = ContainerState.RUNNING;
+                 });
+             });
+ 
 -            const crName = 'test_container_request';
 -            createContainerRequest(
 -                activeUser,
 -                crName,
 -                'arvados/jobs',
 -                ['echo', 'hello world'],
 -                false, 'Committed')
 -            .then(function(containerRequest) {
++            const crName = "test_container_request";
++            createContainerRequest(activeUser, crName, "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (containerRequest) {
+                 // Create empty log file before loading process page
 -                cy.appendLog(adminUser.token, containerRequest.uuid, "stdout.txt", [
 -                    ""
 -                ])
++                cy.appendLog(adminUser.token, containerRequest.uuid, "stdout.txt", [""]);
  
-                 cy.getContainer(activeUser.token, containerRequest.container_uuid).then(function (queuedContainer) {
-                     expect(queuedContainer.state).to.be.equal("Queued");
+                 cy.loginAs(activeUser);
+                 cy.goToPath(`/processes/${containerRequest.uuid}`);
 -                cy.get('[data-cy=process-details]').should('contain', crName);
 -                cy.get('[data-cy=process-logs]')
 -                    .should('contain', 'No logs yet')
 -                    .and('not.contain', 'hello world');
++                cy.get("[data-cy=process-details]").should("contain", crName);
++                cy.get("[data-cy=process-logs]").should("contain", "No logs yet").and("not.contain", "hello world");
+ 
+                 // Append a log line
 -                cy.appendLog(adminUser.token, containerRequest.uuid, "stdout.txt", [
 -                    "2023-07-18T20:14:48.128642814Z hello world"
 -                ]).then(() => {
 -                    cy.get('[data-cy=process-logs]', {timeout: 7000})
 -                        .should('not.contain', 'No logs yet')
 -                        .and('contain', 'hello world');
++                cy.appendLog(adminUser.token, containerRequest.uuid, "stdout.txt", ["2023-07-18T20:14:48.128642814Z hello world"]).then(() => {
++                    cy.get("[data-cy=process-logs]", { timeout: 7000 }).should("not.contain", "No logs yet").and("contain", "hello world");
                  });
-                 cy.updateContainer(adminUser.token, containerRequest.container_uuid, {
-                     state: "Locked",
-                 }).then(function (lockedContainer) {
-                     expect(lockedContainer.state).to.be.equal("Locked");
- 
-                     cy.updateContainer(adminUser.token, lockedContainer.uuid, {
-                         state: "Running",
-                         runtime_status: {
-                             error: "Something went wrong",
-                             errorDetail: "Process exited with status 1",
-                             warning: "Free disk space is low",
-                         },
-                     })
-                         .as("runningContainer")
-                         .then(function (runningContainer) {
-                             expect(runningContainer.state).to.be.equal("Running");
-                             expect(runningContainer.runtime_status).to.be.deep.equal({
-                                 error: "Something went wrong",
-                                 errorDetail: "Process exited with status 1",
-                                 warning: "Free disk space is low",
-                             });
-                         });
+ 
+                 // Append new log line to different file
 -                cy.appendLog(adminUser.token, containerRequest.uuid, "stderr.txt", [
 -                    "2023-07-18T20:14:49.128642814Z hello new line"
 -                ]).then(() => {
 -                    cy.get('[data-cy=process-logs]', {timeout: 7000})
 -                        .should('not.contain', 'No logs yet')
 -                        .and('contain', 'hello new line');
++                cy.appendLog(adminUser.token, containerRequest.uuid, "stderr.txt", ["2023-07-18T20:14:49.128642814Z hello new line"]).then(() => {
++                    cy.get("[data-cy=process-logs]", { timeout: 7000 }).should("not.contain", "No logs yet").and("contain", "hello new line");
                  });
              });
-         // Test that the UI shows the error and warning messages
-         cy.getAll("@containerRequest", "@runningContainer").then(function ([containerRequest]) {
-             cy.loginAs(activeUser);
-             cy.goToPath(`/processes/${containerRequest.uuid}`);
-             cy.get("[data-cy=process-runtime-status-error]").should("contain", "Something went wrong").and("contain", "Process exited with status 1");
-             cy.get("[data-cy=process-runtime-status-warning]")
-                 .should("contain", "Free disk space is low")
-                 .and("contain", "No additional warning details available");
          });
  
-         // Force container_count for testing
-         let containerCount = 2;
-         cy.intercept({ method: "GET", url: "**/arvados/v1/container_requests/*" }, req => {
-             req.reply(res => {
-                 res.body.container_count = containerCount;
 -        it('filters process logs by event type', function() {
++        it("filters process logs by event type", function () {
+             const nodeInfoLogs = [
 -                'Host Information',
 -                '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',
 -                'CPU Information',
 -                'processor  : 0',
 -                'vendor_id  : GenuineIntel',
 -                'cpu family : 6',
 -                'model      : 79',
 -                'model name : Intel(R) Xeon(R) CPU E5-2673 v4 @ 2.30GHz'
++                "Host Information",
++                "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",
++                "CPU Information",
++                "processor  : 0",
++                "vendor_id  : GenuineIntel",
++                "cpu family : 6",
++                "model      : 79",
++                "model name : Intel(R) Xeon(R) CPU E5-2673 v4 @ 2.30GHz",
+             ];
+             const crunchRunLogs = [
 -                '2022-03-22T13:56:22.542417997Z using local keepstore process (pid 3733) at http://localhost:46837, writing logs to keepstore.txt in log collection',
 -                '2022-03-22T13:56:26.237571754Z crunch-run 2.4.0~dev20220321141729 (go1.17.1) started',
 -                '2022-03-22T13:56:26.244704134Z crunch-run process has uid=0(root) gid=0(root) groups=0(root)',
 -                '2022-03-22T13:56:26.244862836Z Executing container \'zzzzz-dz642-1wokwvcct9s9du3\' using docker runtime',
 -                '2022-03-22T13:56:26.245037738Z Executing on host \'compute-99cb150b26149780de44b929577e1aed-19rgca8vobuvc4p\'',
++                "2022-03-22T13:56:22.542417997Z using local keepstore process (pid 3733) at http://localhost:46837, writing logs to keepstore.txt in log collection",
++                "2022-03-22T13:56:26.237571754Z crunch-run 2.4.0~dev20220321141729 (go1.17.1) started",
++                "2022-03-22T13:56:26.244704134Z crunch-run process has uid=0(root) gid=0(root) groups=0(root)",
++                "2022-03-22T13:56:26.244862836Z Executing container 'zzzzz-dz642-1wokwvcct9s9du3' using docker runtime",
++                "2022-03-22T13:56:26.245037738Z Executing on host 'compute-99cb150b26149780de44b929577e1aed-19rgca8vobuvc4p'",
+             ];
+             const stdoutLogs = [
 -                '2022-03-22T13:56:22.542417987Z Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec dui nisi, hendrerit porta sapien a, pretium dignissim purus.',
 -                '2022-03-22T13:56:22.542417997Z Integer viverra, mauris finibus aliquet ultricies, dui mauris cursus justo, ut venenatis nibh ex eget neque.',
 -                '2022-03-22T13:56:22.542418007Z In hac habitasse platea dictumst.',
 -                '2022-03-22T13:56:22.542418027Z Fusce fringilla turpis id accumsan faucibus. Donec congue congue ex non posuere. In semper mi quis tristique rhoncus.',
 -                '2022-03-22T13:56:22.542418037Z Interdum et malesuada fames ac ante ipsum primis in faucibus.',
 -                '2022-03-22T13:56:22.542418047Z Quisque fermentum tortor ex, ut suscipit velit feugiat faucibus.',
 -                '2022-03-22T13:56:22.542418057Z Donec vitae porta risus, at luctus nulla. Mauris gravida iaculis ipsum, id sagittis tortor egestas ac.',
 -                '2022-03-22T13:56:22.542418067Z Maecenas condimentum volutpat nulla. Integer lacinia maximus risus eu posuere.',
 -                '2022-03-22T13:56:22.542418077Z Donec vitae leo id augue gravida bibendum.',
 -                '2022-03-22T13:56:22.542418087Z Nam libero libero, pretium ac faucibus elementum, mattis nec ex.',
 -                '2022-03-22T13:56:22.542418097Z Nullam id laoreet nibh. Vivamus tellus metus, pretium quis justo ut, bibendum varius metus. Pellentesque vitae accumsan lorem, quis tincidunt augue.',
 -                '2022-03-22T13:56:22.542418107Z Aliquam viverra nisi nulla, et efficitur dolor mattis in.',
 -                '2022-03-22T13:56:22.542418117Z 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.',
 -                '2022-03-22T13:56:22.542418127Z 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.',
 -                '2022-03-22T13:56:22.542418137Z Phasellus non ex quis arcu tempus faucibus molestie in sapien.',
 -                '2022-03-22T13:56:22.542418147Z Duis tristique semper dolor, vitae pulvinar risus.',
 -                '2022-03-22T13:56:22.542418157Z Aliquam tortor elit, luctus nec tortor eget, porta tristique nulla.',
 -                '2022-03-22T13:56:22.542418167Z Nulla eget mollis ipsum.',
++                "2022-03-22T13:56:22.542417987Z Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec dui nisi, hendrerit porta sapien a, pretium dignissim purus.",
++                "2022-03-22T13:56:22.542417997Z Integer viverra, mauris finibus aliquet ultricies, dui mauris cursus justo, ut venenatis nibh ex eget neque.",
++                "2022-03-22T13:56:22.542418007Z In hac habitasse platea dictumst.",
++                "2022-03-22T13:56:22.542418027Z Fusce fringilla turpis id accumsan faucibus. Donec congue congue ex non posuere. In semper mi quis tristique rhoncus.",
++                "2022-03-22T13:56:22.542418037Z Interdum et malesuada fames ac ante ipsum primis in faucibus.",
++                "2022-03-22T13:56:22.542418047Z Quisque fermentum tortor ex, ut suscipit velit feugiat faucibus.",
++                "2022-03-22T13:56:22.542418057Z Donec vitae porta risus, at luctus nulla. Mauris gravida iaculis ipsum, id sagittis tortor egestas ac.",
++                "2022-03-22T13:56:22.542418067Z Maecenas condimentum volutpat nulla. Integer lacinia maximus risus eu posuere.",
++                "2022-03-22T13:56:22.542418077Z Donec vitae leo id augue gravida bibendum.",
++                "2022-03-22T13:56:22.542418087Z Nam libero libero, pretium ac faucibus elementum, mattis nec ex.",
++                "2022-03-22T13:56:22.542418097Z Nullam id laoreet nibh. Vivamus tellus metus, pretium quis justo ut, bibendum varius metus. Pellentesque vitae accumsan lorem, quis tincidunt augue.",
++                "2022-03-22T13:56:22.542418107Z Aliquam viverra nisi nulla, et efficitur dolor mattis in.",
++                "2022-03-22T13:56:22.542418117Z 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.",
++                "2022-03-22T13:56:22.542418127Z 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.",
++                "2022-03-22T13:56:22.542418137Z Phasellus non ex quis arcu tempus faucibus molestie in sapien.",
++                "2022-03-22T13:56:22.542418147Z Duis tristique semper dolor, vitae pulvinar risus.",
++                "2022-03-22T13:56:22.542418157Z Aliquam tortor elit, luctus nec tortor eget, porta tristique nulla.",
++                "2022-03-22T13:56:22.542418167Z Nulla eget mollis ipsum.",
+             ];
+ 
 -            createContainerRequest(
 -                activeUser,
 -                'test_container_request',
 -                'arvados/jobs',
 -                ['echo', 'hello world'],
 -                false, 'Committed')
 -            .then(function(containerRequest) {
 -                cy.appendLog(adminUser.token, containerRequest.uuid, "node-info.txt", nodeInfoLogs).as('nodeInfoLogs');
 -                cy.appendLog(adminUser.token, containerRequest.uuid, "crunch-run.txt", crunchRunLogs).as('crunchRunLogs');
 -                cy.appendLog(adminUser.token, containerRequest.uuid, "stdout.txt", stdoutLogs).as('stdoutLogs');
 -
 -                cy.getAll('@stdoutLogs', '@nodeInfoLogs', '@crunchRunLogs').then(function() {
++            createContainerRequest(activeUser, "test_container_request", "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (
++                containerRequest
++            ) {
++                cy.appendLog(adminUser.token, containerRequest.uuid, "node-info.txt", nodeInfoLogs).as("nodeInfoLogs");
++                cy.appendLog(adminUser.token, containerRequest.uuid, "crunch-run.txt", crunchRunLogs).as("crunchRunLogs");
++                cy.appendLog(adminUser.token, containerRequest.uuid, "stdout.txt", stdoutLogs).as("stdoutLogs");
++
++                cy.getAll("@stdoutLogs", "@nodeInfoLogs", "@crunchRunLogs").then(function () {
+                     cy.loginAs(activeUser);
+                     cy.goToPath(`/processes/${containerRequest.uuid}`);
+                     // Should show main logs by default
 -                    cy.get('[data-cy=process-logs-filter]', {timeout: 7000}).should('contain', 'Main logs');
 -                    cy.get('[data-cy=process-logs]')
 -                        .should('contain', stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
 -                        .and('not.contain', nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
 -                        .and('contain', crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
++                    cy.get("[data-cy=process-logs-filter]", { timeout: 7000 }).should("contain", "Main logs");
++                    cy.get("[data-cy=process-logs]")
++                        .should("contain", stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
++                        .and("not.contain", nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
++                        .and("contain", crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
+                     // Select 'All logs'
 -                    cy.get('[data-cy=process-logs-filter]').click();
 -                    cy.get('body').contains('li', 'All logs').click();
 -                    cy.get('[data-cy=process-logs]')
 -                        .should('contain', stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
 -                        .and('contain', nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
 -                        .and('contain', crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
++                    cy.get("[data-cy=process-logs-filter]").click();
++                    cy.get("body").contains("li", "All logs").click();
++                    cy.get("[data-cy=process-logs]")
++                        .should("contain", stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
++                        .and("contain", nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
++                        .and("contain", crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
+                     // Select 'node-info' logs
 -                    cy.get('[data-cy=process-logs-filter]').click();
 -                    cy.get('body').contains('li', 'node-info').click();
 -                    cy.get('[data-cy=process-logs]')
 -                        .should('not.contain', stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
 -                        .and('contain', nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
 -                        .and('not.contain', crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
++                    cy.get("[data-cy=process-logs-filter]").click();
++                    cy.get("body").contains("li", "node-info").click();
++                    cy.get("[data-cy=process-logs]")
++                        .should("not.contain", stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
++                        .and("contain", nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
++                        .and("not.contain", crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
+                     // Select 'stdout' logs
 -                    cy.get('[data-cy=process-logs-filter]').click();
 -                    cy.get('body').contains('li', 'stdout').click();
 -                    cy.get('[data-cy=process-logs]')
 -                        .should('contain', stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
 -                        .and('not.contain', nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
 -                        .and('not.contain', crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
++                    cy.get("[data-cy=process-logs-filter]").click();
++                    cy.get("body").contains("li", "stdout").click();
++                    cy.get("[data-cy=process-logs]")
++                        .should("contain", stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
++                        .and("not.contain", nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
++                        .and("not.contain", crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
+                 });
              });
          });
  
-         cy.getAll("@containerRequest").then(function ([containerRequest]) {
-             cy.goToPath(`/processes/${containerRequest.uuid}`);
-             cy.get("[data-cy=process-runtime-status-retry-warning]", { timeout: 7000 }).should("contain", "Process retried 1 time");
 -        it('sorts combined logs', function() {
 -            const crName = 'test_container_request';
 -            createContainerRequest(
 -                activeUser,
 -                crName,
 -                'arvados/jobs',
 -                ['echo', 'hello world'],
 -                false, 'Committed')
 -            .then(function(containerRequest) {
++        it("sorts combined logs", function () {
++            const crName = "test_container_request";
++            createContainerRequest(activeUser, crName, "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (containerRequest) {
+                 cy.appendLog(adminUser.token, containerRequest.uuid, "node-info.txt", [
+                     "3: nodeinfo 1",
+                     "2: nodeinfo 2",
+                     "1: nodeinfo 3",
+                     "2: nodeinfo 4",
+                     "3: nodeinfo 5",
 -                ]).as('node-info');
++                ]).as("node-info");
+ 
+                 cy.appendLog(adminUser.token, containerRequest.uuid, "stdout.txt", [
+                     "2023-07-18T20:14:48.128642814Z first",
 -                    "2023-07-18T20:14:49.128642814Z third"
 -                ]).as('stdout');
++                    "2023-07-18T20:14:49.128642814Z third",
++                ]).as("stdout");
+ 
 -                cy.appendLog(adminUser.token, containerRequest.uuid, "stderr.txt", [
 -                    "2023-07-18T20:14:48.528642814Z second"
 -                ]).as('stderr');
++                cy.appendLog(adminUser.token, containerRequest.uuid, "stderr.txt", ["2023-07-18T20:14:48.528642814Z second"]).as("stderr");
+ 
+                 cy.loginAs(activeUser);
+                 cy.goToPath(`/processes/${containerRequest.uuid}`);
 -                cy.get('[data-cy=process-details]').should('contain', crName);
 -                cy.get('[data-cy=process-logs]')
 -                    .should('contain', 'No logs yet');
++                cy.get("[data-cy=process-details]").should("contain", crName);
++                cy.get("[data-cy=process-logs]").should("contain", "No logs yet");
+ 
 -                cy.getAll('@node-info', '@stdout', '@stderr').then(() => {
++                cy.getAll("@node-info", "@stdout", "@stderr").then(() => {
+                     // Verify sorted main logs
 -                    cy.get('[data-cy=process-logs] pre', {timeout: 7000})
 -                        .eq(0).should('contain', '2023-07-18T20:14:48.128642814Z first');
 -                    cy.get('[data-cy=process-logs] pre')
 -                        .eq(1).should('contain', '2023-07-18T20:14:48.528642814Z second');
 -                    cy.get('[data-cy=process-logs] pre')
 -                        .eq(2).should('contain', '2023-07-18T20:14:49.128642814Z third');
++                    cy.get("[data-cy=process-logs] pre", { timeout: 7000 }).eq(0).should("contain", "2023-07-18T20:14:48.128642814Z first");
++                    cy.get("[data-cy=process-logs] pre").eq(1).should("contain", "2023-07-18T20:14:48.528642814Z second");
++                    cy.get("[data-cy=process-logs] pre").eq(2).should("contain", "2023-07-18T20:14:49.128642814Z third");
+ 
+                     // Switch to All logs
 -                    cy.get('[data-cy=process-logs-filter]').click();
 -                    cy.get('body').contains('li', 'All logs').click();
++                    cy.get("[data-cy=process-logs-filter]").click();
++                    cy.get("body").contains("li", "All logs").click();
+                     // Verify non-sorted lines were preserved
 -                    cy.get('[data-cy=process-logs] pre')
 -                        .eq(0).should('contain', '3: nodeinfo 1');
 -                    cy.get('[data-cy=process-logs] pre')
 -                        .eq(1).should('contain', '2: nodeinfo 2');
 -                    cy.get('[data-cy=process-logs] pre')
 -                        .eq(2).should('contain', '1: nodeinfo 3');
 -                    cy.get('[data-cy=process-logs] pre')
 -                        .eq(3).should('contain', '2: nodeinfo 4');
 -                    cy.get('[data-cy=process-logs] pre')
 -                        .eq(4).should('contain', '3: nodeinfo 5');
++                    cy.get("[data-cy=process-logs] pre").eq(0).should("contain", "3: nodeinfo 1");
++                    cy.get("[data-cy=process-logs] pre").eq(1).should("contain", "2: nodeinfo 2");
++                    cy.get("[data-cy=process-logs] pre").eq(2).should("contain", "1: nodeinfo 3");
++                    cy.get("[data-cy=process-logs] pre").eq(3).should("contain", "2: nodeinfo 4");
++                    cy.get("[data-cy=process-logs] pre").eq(4).should("contain", "3: nodeinfo 5");
+                     // Verify sorted logs
 -                    cy.get('[data-cy=process-logs] pre')
 -                        .eq(5).should('contain', '2023-07-18T20:14:48.128642814Z first');
 -                    cy.get('[data-cy=process-logs] pre')
 -                        .eq(6).should('contain', '2023-07-18T20:14:48.528642814Z second');
 -                    cy.get('[data-cy=process-logs] pre')
 -                        .eq(7).should('contain', '2023-07-18T20:14:49.128642814Z third');
++                    cy.get("[data-cy=process-logs] pre").eq(5).should("contain", "2023-07-18T20:14:48.128642814Z first");
++                    cy.get("[data-cy=process-logs] pre").eq(6).should("contain", "2023-07-18T20:14:48.528642814Z second");
++                    cy.get("[data-cy=process-logs] pre").eq(7).should("contain", "2023-07-18T20:14:49.128642814Z third");
+                 });
+             });
          });
  
-         cy.getAll("@containerRequest").then(function ([containerRequest]) {
-             containerCount = 3;
-             cy.goToPath(`/processes/${containerRequest.uuid}`);
-             cy.get("[data-cy=process-runtime-status-retry-warning]", { timeout: 7000 }).should("contain", "Process retried 2 times");
 -        it('correctly generates sniplines', function() {
++        it("correctly generates sniplines", function () {
+             const SNIPLINE = `================ ✀ ================ ✀ ========= Some log(s) were skipped ========= ✀ ================ ✀ ================`;
 -            const crName = 'test_container_request';
 -            createContainerRequest(
 -                activeUser,
 -                crName,
 -                'arvados/jobs',
 -                ['echo', 'hello world'],
 -                false, 'Committed')
 -            .then(function(containerRequest) {
 -
++            const crName = "test_container_request";
++            createContainerRequest(activeUser, crName, "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (containerRequest) {
+                 cy.appendLog(adminUser.token, containerRequest.uuid, "stdout.txt", [
 -                    'X'.repeat(63999) + '_' +
 -                    'O'.repeat(100) +
 -                    '_' + 'X'.repeat(63999)
 -                ]).as('stdout');
++                    "X".repeat(63999) + "_" + "O".repeat(100) + "_" + "X".repeat(63999),
++                ]).as("stdout");
+ 
+                 cy.loginAs(activeUser);
+                 cy.goToPath(`/processes/${containerRequest.uuid}`);
 -                cy.get('[data-cy=process-details]').should('contain', crName);
 -                cy.get('[data-cy=process-logs]')
 -                    .should('contain', 'No logs yet');
++                cy.get("[data-cy=process-details]").should("contain", crName);
++                cy.get("[data-cy=process-logs]").should("contain", "No logs yet");
+ 
+                 // Switch to stdout since lines are unsortable (no timestamp)
 -                cy.get('[data-cy=process-logs-filter]').click();
 -                cy.get('body').contains('li', 'stdout').click();
++                cy.get("[data-cy=process-logs-filter]").click();
++                cy.get("body").contains("li", "stdout").click();
+ 
 -                cy.getAll('@stdout').then(() => {
++                cy.getAll("@stdout").then(() => {
+                     // Verify first 64KB and snipline
 -                    cy.get('[data-cy=process-logs] pre', {timeout: 7000})
 -                        .eq(0).should('contain', 'X'.repeat(63999) + '_\n' + SNIPLINE);
++                    cy.get("[data-cy=process-logs] pre", { timeout: 7000 })
++                        .eq(0)
++                        .should("contain", "X".repeat(63999) + "_\n" + SNIPLINE);
+                     // Verify last 64KB
 -                    cy.get('[data-cy=process-logs] pre')
 -                        .eq(1).should('contain', '_' + 'X'.repeat(63999));
++                    cy.get("[data-cy=process-logs] pre")
++                        .eq(1)
++                        .should("contain", "_" + "X".repeat(63999));
+                     // Verify none of the Os got through
 -                    cy.get('[data-cy=process-logs] pre')
 -                        .should('not.contain', 'O');
++                    cy.get("[data-cy=process-logs] pre").should("not.contain", "O");
+                 });
+             });
          });
 -
      });
  
-     const testInputs = [
-         {
-             definition: {
-                 id: "#main/input_file",
-                 label: "Label Description",
-                 type: "File",
 -    describe('I/O panel', function() {
++    describe("I/O panel", function () {
+         const testInputs = [
+             {
+                 definition: {
 -                    "id": "#main/input_file",
 -                    "label": "Label Description",
 -                    "type": "File"
++                    id: "#main/input_file",
++                    label: "Label Description",
++                    type: "File",
+                 },
+                 input: {
 -                    "input_file": {
 -                        "basename": "input1.tar",
 -                        "class": "File",
 -                        "location": "keep:00000000000000000000000000000000+01/input1.tar",
 -                        "secondaryFiles": [
++                    input_file: {
++                        basename: "input1.tar",
++                        class: "File",
++                        location: "keep:00000000000000000000000000000000+01/input1.tar",
++                        secondaryFiles: [
+                             {
 -                                "basename": "input1-2.txt",
 -                                "class": "File",
 -                                "location": "keep:00000000000000000000000000000000+01/input1-2.txt"
++                                basename: "input1-2.txt",
++                                class: "File",
++                                location: "keep:00000000000000000000000000000000+01/input1-2.txt",
+                             },
+                             {
 -                                "basename": "input1-3.txt",
 -                                "class": "File",
 -                                "location": "keep:00000000000000000000000000000000+01/input1-3.txt"
++                                basename: "input1-3.txt",
++                                class: "File",
++                                location: "keep:00000000000000000000000000000000+01/input1-3.txt",
+                             },
+                             {
 -                                "basename": "input1-4.txt",
 -                                "class": "File",
 -                                "location": "keep:00000000000000000000000000000000+01/input1-4.txt"
 -                            }
 -                        ]
 -                    }
 -                }
++                                basename: "input1-4.txt",
++                                class: "File",
++                                location: "keep:00000000000000000000000000000000+01/input1-4.txt",
++                            },
++                        ],
++                    },
++                },
+             },
+             {
+                 definition: {
 -                    "id": "#main/input_dir",
 -                    "doc": "Doc Description",
 -                    "type": "Directory"
++                    id: "#main/input_dir",
++                    doc: "Doc Description",
++                    type: "Directory",
+                 },
+                 input: {
 -                    "input_dir": {
 -                        "basename": "11111111111111111111111111111111+01",
 -                        "class": "Directory",
 -                        "location": "keep:11111111111111111111111111111111+01"
 -                    }
 -                }
++                    input_dir: {
++                        basename: "11111111111111111111111111111111+01",
++                        class: "Directory",
++                        location: "keep:11111111111111111111111111111111+01",
++                    },
++                },
+             },
+             {
+                 definition: {
 -                    "id": "#main/input_bool",
 -                    "doc": ["Doc desc 1", "Doc desc 2"],
 -                    "type": "boolean"
++                    id: "#main/input_bool",
++                    doc: ["Doc desc 1", "Doc desc 2"],
++                    type: "boolean",
+                 },
+                 input: {
 -                    "input_bool": true,
 -                }
++                    input_bool: true,
++                },
+             },
+             {
+                 definition: {
 -                    "id": "#main/input_int",
 -                    "type": "int"
++                    id: "#main/input_int",
++                    type: "int",
+                 },
+                 input: {
 -                    "input_int": 1,
 -                }
++                    input_int: 1,
++                },
              },
-             input: {
-                 input_file: {
-                     basename: "input1.tar",
-                     class: "File",
-                     location: "keep:00000000000000000000000000000000+01/input1.tar",
-                     secondaryFiles: [
+             {
+                 definition: {
 -                    "id": "#main/input_long",
 -                    "type": "long"
++                    id: "#main/input_long",
++                    type: "long",
+                 },
+                 input: {
 -                    "input_long" : 1,
 -                }
++                    input_long: 1,
++                },
+             },
+             {
+                 definition: {
 -                    "id": "#main/input_float",
 -                    "type": "float"
++                    id: "#main/input_float",
++                    type: "float",
+                 },
+                 input: {
 -                    "input_float": 1.5,
 -                }
++                    input_float: 1.5,
++                },
+             },
+             {
+                 definition: {
 -                    "id": "#main/input_double",
 -                    "type": "double"
++                    id: "#main/input_double",
++                    type: "double",
+                 },
+                 input: {
 -                    "input_double": 1.3,
 -                }
++                    input_double: 1.3,
++                },
+             },
+             {
+                 definition: {
 -                    "id": "#main/input_string",
 -                    "type": "string"
++                    id: "#main/input_string",
++                    type: "string",
+                 },
+                 input: {
 -                    "input_string": "Hello World",
 -                }
++                    input_string: "Hello World",
++                },
+             },
+             {
+                 definition: {
 -                    "id": "#main/input_file_array",
 -                    "type": {
 -                      "items": "File",
 -                      "type": "array"
 -                    }
++                    id: "#main/input_file_array",
++                    type: {
++                        items: "File",
++                        type: "array",
++                    },
+                 },
+                 input: {
 -                    "input_file_array": [
++                    input_file_array: [
                          {
-                             basename: "input1-2.txt",
 -                            "basename": "input2.tar",
 -                            "class": "File",
 -                            "location": "keep:00000000000000000000000000000000+02/input2.tar"
++                            basename: "input2.tar",
 +                            class: "File",
-                             location: "keep:00000000000000000000000000000000+01/input1-2.txt",
++                            location: "keep:00000000000000000000000000000000+02/input2.tar",
                          },
                          {
-                             basename: "input1-3.txt",
 -                            "basename": "input3.tar",
 -                            "class": "File",
 -                            "location": "keep:00000000000000000000000000000000+03/input3.tar",
 -                            "secondaryFiles": [
++                            basename: "input3.tar",
 +                            class: "File",
-                             location: "keep:00000000000000000000000000000000+01/input1-3.txt",
++                            location: "keep:00000000000000000000000000000000+03/input3.tar",
++                            secondaryFiles: [
+                                 {
 -                                    "basename": "input3-2.txt",
 -                                    "class": "File",
 -                                    "location": "keep:00000000000000000000000000000000+03/input3-2.txt"
 -                                }
 -                            ]
++                                    basename: "input3-2.txt",
++                                    class: "File",
++                                    location: "keep:00000000000000000000000000000000+03/input3-2.txt",
++                                },
++                            ],
                          },
                          {
-                             basename: "input1-4.txt",
-                             class: "File",
-                             location: "keep:00000000000000000000000000000000+01/input1-4.txt",
 -                            "$import": "import_path"
 -                        }
 -                    ]
 -                }
++                            $import: "import_path",
 +                        },
 +                    ],
 +                },
              },
-         },
-         {
-             definition: {
-                 id: "#main/input_dir",
-                 doc: "Doc Description",
-                 type: "Directory",
-             },
-             input: {
-                 input_dir: {
-                     basename: "11111111111111111111111111111111+01",
-                     class: "Directory",
-                     location: "keep:11111111111111111111111111111111+01",
+             {
+                 definition: {
 -                    "id": "#main/input_dir_array",
 -                    "type": {
 -                      "items": "Directory",
 -                      "type": "array"
 -                    }
++                    id: "#main/input_dir_array",
++                    type: {
++                        items: "Directory",
++                        type: "array",
++                    },
+                 },
+                 input: {
 -                    "input_dir_array": [
++                    input_dir_array: [
+                         {
 -                            "basename": "11111111111111111111111111111111+02",
 -                            "class": "Directory",
 -                            "location": "keep:11111111111111111111111111111111+02"
++                            basename: "11111111111111111111111111111111+02",
++                            class: "Directory",
++                            location: "keep:11111111111111111111111111111111+02",
+                         },
+                         {
 -                            "basename": "11111111111111111111111111111111+03",
 -                            "class": "Directory",
 -                            "location": "keep:11111111111111111111111111111111+03"
++                            basename: "11111111111111111111111111111111+03",
++                            class: "Directory",
++                            location: "keep:11111111111111111111111111111111+03",
+                         },
+                         {
 -                            "$import": "import_path"
 -                        }
 -                    ]
 -                }
++                            $import: "import_path",
++                        },
++                    ],
 +                },
              },
-         },
-         {
-             definition: {
-                 id: "#main/input_bool",
-                 doc: ["Doc desc 1", "Doc desc 2"],
-                 type: "boolean",
-             },
-             input: {
-                 input_bool: true,
+             {
+                 definition: {
 -                    "id": "#main/input_int_array",
 -                    "type": {
 -                      "items": "int",
 -                      "type": "array"
 -                    }
++                    id: "#main/input_int_array",
++                    type: {
++                        items: "int",
++                        type: "array",
++                    },
+                 },
+                 input: {
 -                    "input_int_array": [
++                    input_int_array: [
+                         1,
+                         3,
+                         5,
+                         {
 -                            "$import": "import_path"
 -                        }
 -                    ]
 -                }
++                            $import: "import_path",
++                        },
++                    ],
++                },
              },
-         },
-         {
-             definition: {
-                 id: "#main/input_int",
-                 type: "int",
+             {
+                 definition: {
 -                    "id": "#main/input_long_array",
 -                    "type": {
 -                      "items": "long",
 -                      "type": "array"
 -                    }
++                    id: "#main/input_long_array",
++                    type: {
++                        items: "long",
++                        type: "array",
++                    },
+                 },
+                 input: {
 -                    "input_long_array": [
++                    input_long_array: [
+                         10,
+                         20,
+                         {
 -                            "$import": "import_path"
 -                        }
 -                    ]
 -                }
++                            $import: "import_path",
++                        },
++                    ],
++                },
              },
-             input: {
-                 input_int: 1,
+             {
+                 definition: {
 -                    "id": "#main/input_float_array",
 -                    "type": {
 -                      "items": "float",
 -                      "type": "array"
 -                    }
++                    id: "#main/input_float_array",
++                    type: {
++                        items: "float",
++                        type: "array",
++                    },
+                 },
+                 input: {
 -                    "input_float_array": [
++                    input_float_array: [
+                         10.2,
+                         10.4,
+                         10.6,
+                         {
 -                            "$import": "import_path"
 -                        }
 -                    ]
 -                }
++                            $import: "import_path",
++                        },
++                    ],
++                },
              },
-         },
-         {
-             definition: {
-                 id: "#main/input_long",
-                 type: "long",
+             {
+                 definition: {
 -                    "id": "#main/input_double_array",
 -                    "type": {
 -                      "items": "double",
 -                      "type": "array"
 -                    }
++                    id: "#main/input_double_array",
++                    type: {
++                        items: "double",
++                        type: "array",
++                    },
+                 },
+                 input: {
 -                    "input_double_array": [
++                    input_double_array: [
+                         20.1,
+                         20.2,
+                         20.3,
+                         {
 -                            "$import": "import_path"
 -                        }
 -                    ]
 -                }
++                            $import: "import_path",
++                        },
++                    ],
++                },
              },
-             input: {
-                 input_long: 1,
+             {
+                 definition: {
 -                    "id": "#main/input_string_array",
 -                    "type": {
 -                      "items": "string",
 -                      "type": "array"
 -                    }
++                    id: "#main/input_string_array",
++                    type: {
++                        items: "string",
++                        type: "array",
++                    },
+                 },
+                 input: {
 -                    "input_string_array": [
++                    input_string_array: [
+                         "Hello",
+                         "World",
+                         "!",
+                         {
 -                            "$import": "import_path"
 -                        }
 -                    ]
 -                }
++                            $import: "import_path",
++                        },
++                    ],
++                },
              },
-         },
-         {
-             definition: {
-                 id: "#main/input_float",
-                 type: "float",
+             {
+                 definition: {
 -                    "id": "#main/input_bool_include",
 -                    "type": "boolean"
++                    id: "#main/input_bool_include",
++                    type: "boolean",
+                 },
+                 input: {
 -                    "input_bool_include": {
 -                        "$include": "include_path"
 -                    }
 -                }
++                    input_bool_include: {
++                        $include: "include_path",
++                    },
++                },
              },
-             input: {
-                 input_float: 1.5,
+             {
+                 definition: {
 -                    "id": "#main/input_int_include",
 -                    "type": "int"
++                    id: "#main/input_int_include",
++                    type: "int",
+                 },
+                 input: {
 -                    "input_int_include": {
 -                        "$include": "include_path"
 -                    }
 -                }
++                    input_int_include: {
++                        $include: "include_path",
++                    },
++                },
              },
-         },
-         {
-             definition: {
-                 id: "#main/input_double",
-                 type: "double",
+             {
+                 definition: {
 -                    "id": "#main/input_float_include",
 -                    "type": "float"
++                    id: "#main/input_float_include",
++                    type: "float",
+                 },
+                 input: {
 -                    "input_float_include": {
 -                        "$include": "include_path"
 -                    }
 -                }
++                    input_float_include: {
++                        $include: "include_path",
++                    },
++                },
              },
-             input: {
-                 input_double: 1.3,
+             {
+                 definition: {
 -                    "id": "#main/input_string_include",
 -                    "type": "string"
++                    id: "#main/input_string_include",
++                    type: "string",
+                 },
+                 input: {
 -                    "input_string_include": {
 -                        "$include": "include_path"
 -                    }
 -                }
++                    input_string_include: {
++                        $include: "include_path",
++                    },
++                },
              },
-         },
-         {
-             definition: {
-                 id: "#main/input_string",
-                 type: "string",
+             {
+                 definition: {
 -                    "id": "#main/input_file_include",
 -                    "type": "File"
++                    id: "#main/input_file_include",
++                    type: "File",
+                 },
+                 input: {
 -                    "input_file_include": {
 -                        "$include": "include_path"
 -                    }
 -                }
++                    input_file_include: {
++                        $include: "include_path",
++                    },
++                },
              },
-             input: {
-                 input_string: "Hello World",
+             {
+                 definition: {
 -                    "id": "#main/input_directory_include",
 -                    "type": "Directory"
++                    id: "#main/input_directory_include",
++                    type: "Directory",
+                 },
+                 input: {
 -                    "input_directory_include": {
 -                        "$include": "include_path"
 -                    }
 -                }
++                    input_directory_include: {
++                        $include: "include_path",
++                    },
++                },
              },
-         },
-         {
-             definition: {
-                 id: "#main/input_file_array",
-                 type: {
-                     items: "File",
-                     type: "array",
+             {
+                 definition: {
 -                    "id": "#main/input_file_url",
 -                    "type": "File"
++                    id: "#main/input_file_url",
++                    type: "File",
+                 },
+                 input: {
 -                    "input_file_url": {
 -                        "basename": "index.html",
 -                        "class": "File",
 -                        "location": "http://example.com/index.html"
 -                      }
 -                }
 -            }
++                    input_file_url: {
++                        basename: "index.html",
++                        class: "File",
++                        location: "http://example.com/index.html",
++                    },
 +                },
 +            },
-             input: {
-                 input_file_array: [
-                     {
-                         basename: "input2.tar",
+         ];
+ 
+         const testOutputs = [
+             {
+                 definition: {
 -                    "id": "#main/output_file",
 -                    "label": "Label Description",
 -                    "type": "File"
++                    id: "#main/output_file",
++                    label: "Label Description",
++                    type: "File",
+                 },
+                 output: {
 -                    "output_file": {
 -                        "basename": "cat.png",
 -                        "class": "File",
 -                        "location": "cat.png"
 -                    }
 -                }
++                    output_file: {
++                        basename: "cat.png",
 +                        class: "File",
-                         location: "keep:00000000000000000000000000000000+02/input2.tar",
++                        location: "cat.png",
 +                    },
-                     {
-                         basename: "input3.tar",
++                },
+             },
+             {
+                 definition: {
 -                    "id": "#main/output_file_with_secondary",
 -                    "doc": "Doc Description",
 -                    "type": "File"
++                    id: "#main/output_file_with_secondary",
++                    doc: "Doc Description",
++                    type: "File",
+                 },
+                 output: {
 -                    "output_file_with_secondary": {
 -                        "basename": "main.dat",
 -                        "class": "File",
 -                        "location": "main.dat",
 -                        "secondaryFiles": [
++                    output_file_with_secondary: {
++                        basename: "main.dat",
 +                        class: "File",
-                         location: "keep:00000000000000000000000000000000+03/input3.tar",
++                        location: "main.dat",
 +                        secondaryFiles: [
                              {
-                                 basename: "input3-2.txt",
 -                                "basename": "secondary.dat",
 -                                "class": "File",
 -                                "location": "secondary.dat"
++                                basename: "secondary.dat",
 +                                class: "File",
-                                 location: "keep:00000000000000000000000000000000+03/input3-2.txt",
++                                location: "secondary.dat",
+                             },
+                             {
 -                                "basename": "secondary2.dat",
 -                                "class": "File",
 -                                "location": "secondary2.dat"
 -                            }
 -                        ]
 -                    }
 -                }
++                                basename: "secondary2.dat",
++                                class: "File",
++                                location: "secondary2.dat",
 +                            },
 +                        ],
 +                    },
-                     {
-                         $import: "import_path",
-                     },
-                 ],
-             },
-         },
-         {
-             definition: {
-                 id: "#main/input_dir_array",
-                 type: {
-                     items: "Directory",
-                     type: "array",
 +                },
              },
-             input: {
-                 input_dir_array: [
-                     {
-                         basename: "11111111111111111111111111111111+02",
-                         class: "Directory",
-                         location: "keep:11111111111111111111111111111111+02",
-                     },
-                     {
-                         basename: "11111111111111111111111111111111+03",
+             {
+                 definition: {
 -                    "id": "#main/output_dir",
 -                    "doc": ["Doc desc 1", "Doc desc 2"],
 -                    "type": "Directory"
++                    id: "#main/output_dir",
++                    doc: ["Doc desc 1", "Doc desc 2"],
++                    type: "Directory",
+                 },
+                 output: {
 -                    "output_dir": {
 -                        "basename": "outdir1",
 -                        "class": "Directory",
 -                        "location": "outdir1"
 -                    }
 -                }
++                    output_dir: {
++                        basename: "outdir1",
 +                        class: "Directory",
-                         location: "keep:11111111111111111111111111111111+03",
++                        location: "outdir1",
 +                    },
-                     {
-                         $import: "import_path",
-                     },
-                 ],
-             },
-         },
-         {
-             definition: {
-                 id: "#main/input_int_array",
-                 type: {
-                     items: "int",
-                     type: "array",
 +                },
              },
-             input: {
-                 input_int_array: [
-                     1,
-                     3,
-                     5,
-                     {
-                         $import: "import_path",
-                     },
-                 ],
-             },
-         },
-         {
-             definition: {
-                 id: "#main/input_long_array",
-                 type: {
-                     items: "long",
-                     type: "array",
+             {
+                 definition: {
 -                    "id": "#main/output_bool",
 -                    "type": "boolean"
++                    id: "#main/output_bool",
++                    type: "boolean",
                  },
-             },
-             input: {
-                 input_long_array: [
-                     10,
-                     20,
-                     {
-                         $import: "import_path",
-                     },
-                 ],
-             },
-         },
-         {
-             definition: {
-                 id: "#main/input_float_array",
-                 type: {
-                     items: "float",
-                     type: "array",
+                 output: {
 -                    "output_bool": true
 -                }
++                    output_bool: true,
 +                },
              },
-             input: {
-                 input_float_array: [
-                     10.2,
-                     10.4,
-                     10.6,
-                     {
-                         $import: "import_path",
-                     },
-                 ],
-             },
-         },
-         {
-             definition: {
-                 id: "#main/input_double_array",
-                 type: {
-                     items: "double",
-                     type: "array",
+             {
+                 definition: {
 -                    "id": "#main/output_int",
 -                    "type": "int"
++                    id: "#main/output_int",
++                    type: "int",
                  },
-             },
-             input: {
-                 input_double_array: [
-                     20.1,
-                     20.2,
-                     20.3,
-                     {
-                         $import: "import_path",
-                     },
-                 ],
-             },
-         },
-         {
-             definition: {
-                 id: "#main/input_string_array",
-                 type: {
-                     items: "string",
-                     type: "array",
+                 output: {
 -                    "output_int": 1
 -                }
++                    output_int: 1,
 +                },
              },
-             input: {
-                 input_string_array: [
-                     "Hello",
-                     "World",
-                     "!",
-                     {
-                         $import: "import_path",
-                     },
-                 ],
-             },
-         },
-         {
-             definition: {
-                 id: "#main/input_bool_include",
-                 type: "boolean",
-             },
-             input: {
-                 input_bool_include: {
-                     $include: "include_path",
+             {
+                 definition: {
 -                    "id": "#main/output_long",
 -                    "type": "long"
++                    id: "#main/output_long",
++                    type: "long",
                  },
-             },
-         },
-         {
-             definition: {
-                 id: "#main/input_int_include",
-                 type: "int",
-             },
-             input: {
-                 input_int_include: {
-                     $include: "include_path",
+                 output: {
 -                    "output_long": 1
 -                }
++                    output_long: 1,
 +                },
              },
-         },
-         {
-             definition: {
-                 id: "#main/input_float_include",
-                 type: "float",
-             },
-             input: {
-                 input_float_include: {
-                     $include: "include_path",
+             {
+                 definition: {
 -                    "id": "#main/output_float",
 -                    "type": "float"
++                    id: "#main/output_float",
++                    type: "float",
                  },
-             },
-         },
-         {
-             definition: {
-                 id: "#main/input_string_include",
-                 type: "string",
-             },
-             input: {
-                 input_string_include: {
-                     $include: "include_path",
+                 output: {
 -                    "output_float": 100.5
 -                }
++                    output_float: 100.5,
 +                },
              },
-         },
-         {
-             definition: {
-                 id: "#main/input_file_include",
-                 type: "File",
-             },
-             input: {
-                 input_file_include: {
-                     $include: "include_path",
+             {
+                 definition: {
 -                    "id": "#main/output_double",
 -                    "type": "double"
++                    id: "#main/output_double",
++                    type: "double",
                  },
-             },
-         },
-         {
-             definition: {
-                 id: "#main/input_directory_include",
-                 type: "Directory",
-             },
-             input: {
-                 input_directory_include: {
-                     $include: "include_path",
+                 output: {
 -                    "output_double": 100.3
 -                }
++                    output_double: 100.3,
 +                },
              },
-         },
-         {
-             definition: {
-                 id: "#main/input_file_url",
-                 type: "File",
-             },
-             input: {
-                 input_file_url: {
-                     basename: "index.html",
-                     class: "File",
-                     location: "http://example.com/index.html",
+             {
+                 definition: {
 -                    "id": "#main/output_string",
 -                    "type": "string"
++                    id: "#main/output_string",
++                    type: "string",
                  },
-             },
-         },
-     ];
- 
-     const testOutputs = [
-         {
-             definition: {
-                 id: "#main/output_file",
-                 label: "Label Description",
-                 type: "File",
-             },
-             output: {
-                 output_file: {
-                     basename: "cat.png",
-                     class: "File",
-                     location: "cat.png",
+                 output: {
 -                    "output_string": "Hello output"
 -                }
++                    output_string: "Hello output",
 +                },
              },
-         },
-         {
-             definition: {
-                 id: "#main/output_file_with_secondary",
-                 doc: "Doc Description",
-                 type: "File",
-             },
-             output: {
-                 output_file_with_secondary: {
-                     basename: "main.dat",
-                     class: "File",
-                     location: "main.dat",
-                     secondaryFiles: [
+             {
+                 definition: {
 -                    "id": "#main/output_file_array",
 -                    "type": {
 -                        "items": "File",
 -                        "type": "array"
 -                    }
++                    id: "#main/output_file_array",
++                    type: {
++                        items: "File",
++                        type: "array",
++                    },
+                 },
+                 output: {
 -                    "output_file_array": [
++                    output_file_array: [
                          {
-                             basename: "secondary.dat",
 -                            "basename": "output2.tar",
 -                            "class": "File",
 -                            "location": "output2.tar"
++                            basename: "output2.tar",
 +                            class: "File",
-                             location: "secondary.dat",
++                            location: "output2.tar",
                          },
                          {
-                             basename: "secondary2.dat",
 -                            "basename": "output3.tar",
 -                            "class": "File",
 -                            "location": "output3.tar"
 -                        }
 -                    ]
 -                }
++                            basename: "output3.tar",
 +                            class: "File",
-                             location: "secondary2.dat",
++                            location: "output3.tar",
 +                        },
 +                    ],
 +                },
              },
-         },
-         {
-             definition: {
-                 id: "#main/output_dir",
-                 doc: ["Doc desc 1", "Doc desc 2"],
-                 type: "Directory",
-             },
-             output: {
-                 output_dir: {
-                     basename: "outdir1",
-                     class: "Directory",
-                     location: "outdir1",
+             {
+                 definition: {
 -                    "id": "#main/output_dir_array",
 -                    "type": {
 -                        "items": "Directory",
 -                        "type": "array"
 -                    }
++                    id: "#main/output_dir_array",
++                    type: {
++                        items: "Directory",
++                        type: "array",
++                    },
                  },
-             },
-         },
-         {
-             definition: {
-                 id: "#main/output_bool",
-                 type: "boolean",
-             },
-             output: {
-                 output_bool: true,
-             },
-         },
-         {
-             definition: {
-                 id: "#main/output_int",
-                 type: "int",
-             },
-             output: {
-                 output_int: 1,
-             },
-         },
-         {
-             definition: {
-                 id: "#main/output_long",
-                 type: "long",
-             },
-             output: {
-                 output_long: 1,
-             },
-         },
-         {
-             definition: {
-                 id: "#main/output_float",
-                 type: "float",
-             },
-             output: {
-                 output_float: 100.5,
-             },
-         },
-         {
-             definition: {
-                 id: "#main/output_double",
-                 type: "double",
-             },
-             output: {
-                 output_double: 100.3,
-             },
-         },
-         {
-             definition: {
-                 id: "#main/output_string",
-                 type: "string",
-             },
-             output: {
-                 output_string: "Hello output",
-             },
-         },
-         {
-             definition: {
-                 id: "#main/output_file_array",
-                 type: {
-                     items: "File",
-                     type: "array",
+                 output: {
 -                    "output_dir_array": [
++                    output_dir_array: [
+                         {
 -                            "basename": "outdir2",
 -                            "class": "Directory",
 -                            "location": "outdir2"
++                            basename: "outdir2",
++                            class: "Directory",
++                            location: "outdir2",
+                         },
+                         {
 -                            "basename": "outdir3",
 -                            "class": "Directory",
 -                            "location": "outdir3"
 -                        }
 -                    ]
 -                }
++                            basename: "outdir3",
++                            class: "Directory",
++                            location: "outdir3",
++                        },
++                    ],
 +                },
              },
-             output: {
-                 output_file_array: [
-                     {
-                         basename: "output2.tar",
-                         class: "File",
-                         location: "output2.tar",
-                     },
-                     {
-                         basename: "output3.tar",
-                         class: "File",
-                         location: "output3.tar",
+             {
+                 definition: {
 -                    "id": "#main/output_int_array",
 -                    "type": {
 -                        "items": "int",
 -                        "type": "array"
 -                    }
++                    id: "#main/output_int_array",
++                    type: {
++                        items: "int",
++                        type: "array",
 +                    },
-                 ],
-             },
-         },
-         {
-             definition: {
-                 id: "#main/output_dir_array",
-                 type: {
-                     items: "Directory",
-                     type: "array",
+                 },
+                 output: {
 -                    "output_int_array": [
 -                        10,
 -                        11,
 -                        12
 -                    ]
 -                }
++                    output_int_array: [10, 11, 12],
 +                },
              },
-             output: {
-                 output_dir_array: [
-                     {
-                         basename: "outdir2",
-                         class: "Directory",
-                         location: "outdir2",
-                     },
-                     {
-                         basename: "outdir3",
-                         class: "Directory",
-                         location: "outdir3",
+             {
+                 definition: {
 -                    "id": "#main/output_long_array",
 -                    "type": {
 -                        "items": "long",
 -                        "type": "array"
 -                    }
++                    id: "#main/output_long_array",
++                    type: {
++                        items: "long",
++                        type: "array",
 +                    },
-                 ],
-             },
-         },
-         {
-             definition: {
-                 id: "#main/output_int_array",
-                 type: {
-                     items: "int",
-                     type: "array",
                  },
-             },
-             output: {
-                 output_int_array: [10, 11, 12],
-             },
-         },
-         {
-             definition: {
-                 id: "#main/output_long_array",
-                 type: {
-                     items: "long",
-                     type: "array",
+                 output: {
 -                    "output_long_array": [
 -                        51,
 -                        52
 -                    ]
 -                }
++                    output_long_array: [51, 52],
 +                },
              },
-             output: {
-                 output_long_array: [51, 52],
-             },
-         },
-         {
-             definition: {
-                 id: "#main/output_float_array",
-                 type: {
-                     items: "float",
-                     type: "array",
+             {
+                 definition: {
 -                    "id": "#main/output_float_array",
 -                    "type": {
 -                        "items": "float",
 -                        "type": "array"
 -                    }
++                    id: "#main/output_float_array",
++                    type: {
++                        items: "float",
++                        type: "array",
++                    },
                  },
-             },
-             output: {
-                 output_float_array: [100.2, 100.4, 100.6],
-             },
-         },
-         {
-             definition: {
-                 id: "#main/output_double_array",
-                 type: {
-                     items: "double",
-                     type: "array",
+                 output: {
 -                    "output_float_array": [
 -                        100.2,
 -                        100.4,
 -                        100.6
 -                    ]
 -                }
++                    output_float_array: [100.2, 100.4, 100.6],
 +                },
              },
-             output: {
-                 output_double_array: [100.1, 100.2, 100.3],
-             },
-         },
-         {
-             definition: {
-                 id: "#main/output_string_array",
-                 type: {
-                     items: "string",
-                     type: "array",
+             {
+                 definition: {
 -                    "id": "#main/output_double_array",
 -                    "type": {
 -                        "items": "double",
 -                        "type": "array"
 -                    }
++                    id: "#main/output_double_array",
++                    type: {
++                        items: "double",
++                        type: "array",
++                    },
+                 },
+                 output: {
 -                    "output_double_array": [
 -                        100.1,
 -                        100.2,
 -                        100.3
 -                    ]
 -                }
++                    output_double_array: [100.1, 100.2, 100.3],
 +                },
              },
-             output: {
-                 output_string_array: ["Hello", "Output", "!"],
+             {
+                 definition: {
 -                    "id": "#main/output_string_array",
 -                    "type": {
 -                        "items": "string",
 -                        "type": "array"
 -                    }
++                    id: "#main/output_string_array",
++                    type: {
++                        items: "string",
++                        type: "array",
++                    },
+                 },
+                 output: {
 -                    "output_string_array": [
 -                        "Hello",
 -                        "Output",
 -                        "!"
 -                    ]
 -                }
 -            }
++                    output_string_array: ["Hello", "Output", "!"],
++                },
 +            },
-         },
-     ];
- 
-     const verifyIOParameter = (name, label, doc, val, collection, multipleRows) => {
-         cy.get("table tr")
-             .contains(name)
-             .parents("tr")
-             .within($mainRow => {
-                 label && cy.contains(label);
- 
-                 if (multipleRows) {
-                     cy.get($mainRow).nextUntil('[data-cy="process-io-param"]').as("secondaryRows");
-                     if (val) {
-                         if (Array.isArray(val)) {
-                             val.forEach(v => cy.get("@secondaryRows").contains(v));
-                         } else {
-                             cy.get("@secondaryRows").contains(val);
+         ];
+ 
+         const verifyIOParameter = (name, label, doc, val, collection, multipleRows) => {
 -            cy.get('table tr').contains(name).parents('tr').within(($mainRow) => {
 -                label && cy.contains(label);
 -
 -                if (multipleRows) {
 -                    cy.get($mainRow).nextUntil('[data-cy="process-io-param"]').as('secondaryRows');
 -                    if (val) {
 -                        if (Array.isArray(val)) {
 -                            val.forEach(v => cy.get('@secondaryRows').contains(v));
 -                        } else {
 -                            cy.get('@secondaryRows').contains(val);
++            cy.get("table tr")
++                .contains(name)
++                .parents("tr")
++                .within($mainRow => {
++                    label && cy.contains(label);
++
++                    if (multipleRows) {
++                        cy.get($mainRow).nextUntil('[data-cy="process-io-param"]').as("secondaryRows");
++                        if (val) {
++                            if (Array.isArray(val)) {
++                                val.forEach(v => cy.get("@secondaryRows").contains(v));
++                            } else {
++                                cy.get("@secondaryRows").contains(val);
++                            }
                          }
--                    }
--                    if (collection) {
-                         cy.get("@secondaryRows").contains(collection);
 -                        cy.get('@secondaryRows').contains(collection);
--                    }
--                } else {
--                    if (val) {
--                        if (Array.isArray(val)) {
--                            val.forEach(v => cy.contains(v));
--                        } else {
--                            cy.contains(val);
++                        if (collection) {
++                            cy.get("@secondaryRows").contains(collection);
++                        }
++                    } else {
++                        if (val) {
++                            if (Array.isArray(val)) {
++                                val.forEach(v => cy.contains(v));
++                            } else {
++                                cy.contains(val);
++                            }
++                        }
++                        if (collection) {
++                            cy.contains(collection);
                          }
                      }
--                    if (collection) {
--                        cy.contains(collection);
--                    }
--                }
-             });
-     };
--
-     const verifyIOParameterImage = (name, url) => {
-         cy.get("table tr")
-             .contains(name)
-             .parents("tr")
-             .within(() => {
-                 cy.get('[alt="Inline Preview"]')
-                     .should("be.visible")
-                     .and($img => {
-                         expect($img[0].naturalWidth).to.be.greaterThan(0);
-                         expect($img[0].src).contains(url);
-                     });
 -
 -            });
++                });
+         };
+ 
+         const verifyIOParameterImage = (name, url) => {
 -            cy.get('table tr').contains(name).parents('tr').within(() => {
 -                cy.get('[alt="Inline Preview"]')
 -                    .should('be.visible')
 -                    .and(($img) => {
 -                        expect($img[0].naturalWidth).to.be.greaterThan(0);
 -                        expect($img[0].src).contains(url);
 -                    })
 -            });
++            cy.get("table tr")
++                .contains(name)
++                .parents("tr")
++                .within(() => {
++                    cy.get('[alt="Inline Preview"]')
++                        .should("be.visible")
++                        .and($img => {
++                            expect($img[0].naturalWidth).to.be.greaterThan(0);
++                            expect($img[0].src).contains(url);
++                        });
++                });
+         };
+ 
 -        it('displays IO parameters with keep links and previews', function() {
++        it("displays IO parameters with keep links and previews", function () {
+             // Create output collection for real files
+             cy.createCollection(adminUser.token, {
+                 name: `Test collection ${Math.floor(Math.random() * 999999)}`,
+                 owner_uuid: activeUser.user.uuid,
 -            }).then((testOutputCollection) => {
 -                        cy.loginAs(activeUser);
++            }).then(testOutputCollection => {
++                cy.loginAs(activeUser);
+ 
 -                        cy.goToPath(`/collections/${testOutputCollection.uuid}`);
++                cy.goToPath(`/collections/${testOutputCollection.uuid}`);
+ 
 -                        cy.get('[data-cy=upload-button]').click();
++                cy.get("[data-cy=upload-button]").click();
+ 
 -                        cy.fixture('files/cat.png', 'base64').then(content => {
 -                            cy.get('[data-cy=drag-and-drop]').upload(content, 'cat.png');
 -                            cy.get('[data-cy=form-submit-btn]').click();
 -                            cy.waitForDom().get('[data-cy=form-submit-btn]').should('not.exist');
 -                            // Confirm final collection state.
 -                            cy.get('[data-cy=collection-files-panel]')
 -                                .contains('cat.png').should('exist');
 -                        });
++                cy.fixture("files/cat.png", "base64").then(content => {
++                    cy.get("[data-cy=drag-and-drop]").upload(content, "cat.png");
++                    cy.get("[data-cy=form-submit-btn]").click();
++                    cy.waitForDom().get("[data-cy=form-submit-btn]").should("not.exist");
++                    // Confirm final collection state.
++                    cy.get("[data-cy=collection-files-panel]").contains("cat.png").should("exist");
++                });
+ 
 -                        cy.getCollection(activeUser.token, testOutputCollection.uuid).as('testOutputCollection');
 -                    });
++                cy.getCollection(activeUser.token, testOutputCollection.uuid).as("testOutputCollection");
 +            });
-     };
  
-     it("displays IO parameters with keep links and previews", function () {
-         // Create output collection for real files
-         cy.createCollection(adminUser.token, {
-             name: `Test collection ${Math.floor(Math.random() * 999999)}`,
-             owner_uuid: activeUser.user.uuid,
-         }).then(testOutputCollection => {
-             cy.loginAs(activeUser);
+             // Get updated collection pdh
 -            cy.getAll('@testOutputCollection').then(([testOutputCollection]) => {
++            cy.getAll("@testOutputCollection").then(([testOutputCollection]) => {
+                 // Add output uuid and inputs to container request
 -                cy.intercept({method: 'GET', url: '**/arvados/v1/container_requests/*'}, (req) => {
 -                    req.reply((res) => {
++                cy.intercept({ method: "GET", url: "**/arvados/v1/container_requests/*" }, req => {
++                    req.reply(res => {
+                         res.body.output_uuid = testOutputCollection.uuid;
+                         res.body.mounts["/var/lib/cwl/cwl.input.json"] = {
 -                            content: testInputs.map((param) => (param.input)).reduce((acc, val) => (Object.assign(acc, val)), {})
++                            content: testInputs.map(param => param.input).reduce((acc, val) => Object.assign(acc, val), {}),
+                         };
+                         res.body.mounts["/var/lib/cwl/workflow.json"] = {
+                             content: {
 -                                $graph: [{
 -                                    id: "#main",
 -                                    inputs: testInputs.map((input) => (input.definition)),
 -                                    outputs: testOutputs.map((output) => (output.definition))
 -                                }]
 -                            }
++                                $graph: [
++                                    {
++                                        id: "#main",
++                                        inputs: testInputs.map(input => input.definition),
++                                        outputs: testOutputs.map(output => output.definition),
++                                    },
++                                ],
++                            },
+                         };
+                     });
+                 });
  
-             cy.goToPath(`/collections/${testOutputCollection.uuid}`);
+                 // Stub fake output collection
 -                cy.intercept({method: 'GET', url: `**/arvados/v1/collections/${testOutputCollection.uuid}*`}, {
 -                    statusCode: 200,
 -                    body: {
 -                        uuid: testOutputCollection.uuid,
 -                        portable_data_hash: testOutputCollection.portable_data_hash,
++                cy.intercept(
++                    { method: "GET", url: `**/arvados/v1/collections/${testOutputCollection.uuid}*` },
++                    {
++                        statusCode: 200,
++                        body: {
++                            uuid: testOutputCollection.uuid,
++                            portable_data_hash: testOutputCollection.portable_data_hash,
++                        },
+                     }
 -                });
++                );
  
-             cy.get("[data-cy=upload-button]").click();
+                 // Stub fake output json
 -                cy.intercept({method: 'GET', url: '**/c%3Dzzzzz-4zz18-zzzzzzzzzzzzzzz/cwl.output.json'}, {
 -                    statusCode: 200,
 -                    body: testOutputs.map((param) => (param.output)).reduce((acc, val) => (Object.assign(acc, val)), {})
 -                });
++                cy.intercept(
++                    { method: "GET", url: "**/c%3Dzzzzz-4zz18-zzzzzzzzzzzzzzz/cwl.output.json" },
++                    {
++                        statusCode: 200,
++                        body: testOutputs.map(param => param.output).reduce((acc, val) => Object.assign(acc, val), {}),
++                    }
++                );
  
-             cy.fixture("files/cat.png", "base64").then(content => {
-                 cy.get("[data-cy=drag-and-drop]").upload(content, "cat.png");
-                 cy.get("[data-cy=form-submit-btn]").click();
-                 cy.waitForDom().get("[data-cy=form-submit-btn]").should("not.exist");
-                 // Confirm final collection state.
-                 cy.get("[data-cy=collection-files-panel]").contains("cat.png").should("exist");
+                 // Stub webdav response, points to output json
 -                cy.intercept({method: 'PROPFIND', url: '*'}, {
 -                    fixture: 'webdav-propfind-outputs.xml',
 -                });
++                cy.intercept(
++                    { method: "PROPFIND", url: "*" },
++                    {
++                        fixture: "webdav-propfind-outputs.xml",
++                    }
++                );
              });
  
-             cy.getCollection(activeUser.token, testOutputCollection.uuid).as("testOutputCollection");
 -            createContainerRequest(
 -                activeUser,
 -                'test_container_request',
 -                'arvados/jobs',
 -                ['echo', 'hello world'],
 -                false, 'Committed')
 -            .as('containerRequest');
++            createContainerRequest(activeUser, "test_container_request", "arvados/jobs", ["echo", "hello world"], false, "Committed").as(
++                "containerRequest"
++            );
+ 
 -            cy.getAll('@containerRequest', '@testOutputCollection').then(function([containerRequest, testOutputCollection]) {
++            cy.getAll("@containerRequest", "@testOutputCollection").then(function ([containerRequest, testOutputCollection]) {
+                 cy.goToPath(`/processes/${containerRequest.uuid}`);
 -                cy.get('[data-cy=process-io-card] h6').contains('Inputs')
 -                    .parents('[data-cy=process-io-card]').within(() => {
 -                        verifyIOParameter('input_file', null, "Label Description", 'input1.tar', '00000000000000000000000000000000+01');
 -                        verifyIOParameter('input_file', null, "Label Description", 'input1-2.txt', undefined, true);
 -                        verifyIOParameter('input_file', null, "Label Description", 'input1-3.txt', undefined, true);
 -                        verifyIOParameter('input_file', null, "Label Description", 'input1-4.txt', undefined, true);
 -                        verifyIOParameter('input_dir', null, "Doc Description", '/', '11111111111111111111111111111111+01');
 -                        verifyIOParameter('input_bool', null, "Doc desc 1, Doc desc 2", 'true');
 -                        verifyIOParameter('input_int', null, null, '1');
 -                        verifyIOParameter('input_long', null, null, '1');
 -                        verifyIOParameter('input_float', null, null, '1.5');
 -                        verifyIOParameter('input_double', null, null, '1.3');
 -                        verifyIOParameter('input_string', null, null, 'Hello World');
 -                        verifyIOParameter('input_file_array', null, null, 'input2.tar', '00000000000000000000000000000000+02');
 -                        verifyIOParameter('input_file_array', null, null, 'input3.tar', undefined, true);
 -                        verifyIOParameter('input_file_array', null, null, 'input3-2.txt', undefined, true);
 -                        verifyIOParameter('input_file_array', null, null, 'Cannot display value', undefined, true);
 -                        verifyIOParameter('input_dir_array', null, null, '/', '11111111111111111111111111111111+02');
 -                        verifyIOParameter('input_dir_array', null, null, '/', '11111111111111111111111111111111+03', true);
 -                        verifyIOParameter('input_dir_array', null, null, 'Cannot display value', undefined, true);
 -                        verifyIOParameter('input_int_array', null, null, ["1", "3", "5", "Cannot display value"]);
 -                        verifyIOParameter('input_long_array', null, null, ["10", "20", "Cannot display value"]);
 -                        verifyIOParameter('input_float_array', null, null, ["10.2", "10.4", "10.6", "Cannot display value"]);
 -                        verifyIOParameter('input_double_array', null, null, ["20.1", "20.2", "20.3", "Cannot display value"]);
 -                        verifyIOParameter('input_string_array', null, null, ["Hello", "World", "!", "Cannot display value"]);
 -                        verifyIOParameter('input_bool_include', null, null, "Cannot display value");
 -                        verifyIOParameter('input_int_include', null, null, "Cannot display value");
 -                        verifyIOParameter('input_float_include', null, null, "Cannot display value");
 -                        verifyIOParameter('input_string_include', null, null, "Cannot display value");
 -                        verifyIOParameter('input_file_include', null, null, "Cannot display value");
 -                        verifyIOParameter('input_directory_include', null, null, "Cannot display value");
 -                        verifyIOParameter('input_file_url', null, null, "http://example.com/index.html");
++                cy.get("[data-cy=process-io-card] h6")
++                    .contains("Inputs")
++                    .parents("[data-cy=process-io-card]")
++                    .within(() => {
++                        verifyIOParameter("input_file", null, "Label Description", "input1.tar", "00000000000000000000000000000000+01");
++                        verifyIOParameter("input_file", null, "Label Description", "input1-2.txt", undefined, true);
++                        verifyIOParameter("input_file", null, "Label Description", "input1-3.txt", undefined, true);
++                        verifyIOParameter("input_file", null, "Label Description", "input1-4.txt", undefined, true);
++                        verifyIOParameter("input_dir", null, "Doc Description", "/", "11111111111111111111111111111111+01");
++                        verifyIOParameter("input_bool", null, "Doc desc 1, Doc desc 2", "true");
++                        verifyIOParameter("input_int", null, null, "1");
++                        verifyIOParameter("input_long", null, null, "1");
++                        verifyIOParameter("input_float", null, null, "1.5");
++                        verifyIOParameter("input_double", null, null, "1.3");
++                        verifyIOParameter("input_string", null, null, "Hello World");
++                        verifyIOParameter("input_file_array", null, null, "input2.tar", "00000000000000000000000000000000+02");
++                        verifyIOParameter("input_file_array", null, null, "input3.tar", undefined, true);
++                        verifyIOParameter("input_file_array", null, null, "input3-2.txt", undefined, true);
++                        verifyIOParameter("input_file_array", null, null, "Cannot display value", undefined, true);
++                        verifyIOParameter("input_dir_array", null, null, "/", "11111111111111111111111111111111+02");
++                        verifyIOParameter("input_dir_array", null, null, "/", "11111111111111111111111111111111+03", true);
++                        verifyIOParameter("input_dir_array", null, null, "Cannot display value", undefined, true);
++                        verifyIOParameter("input_int_array", null, null, ["1", "3", "5", "Cannot display value"]);
++                        verifyIOParameter("input_long_array", null, null, ["10", "20", "Cannot display value"]);
++                        verifyIOParameter("input_float_array", null, null, ["10.2", "10.4", "10.6", "Cannot display value"]);
++                        verifyIOParameter("input_double_array", null, null, ["20.1", "20.2", "20.3", "Cannot display value"]);
++                        verifyIOParameter("input_string_array", null, null, ["Hello", "World", "!", "Cannot display value"]);
++                        verifyIOParameter("input_bool_include", null, null, "Cannot display value");
++                        verifyIOParameter("input_int_include", null, null, "Cannot display value");
++                        verifyIOParameter("input_float_include", null, null, "Cannot display value");
++                        verifyIOParameter("input_string_include", null, null, "Cannot display value");
++                        verifyIOParameter("input_file_include", null, null, "Cannot display value");
++                        verifyIOParameter("input_directory_include", null, null, "Cannot display value");
++                        verifyIOParameter("input_file_url", null, null, "http://example.com/index.html");
+                     });
 -                cy.get('[data-cy=process-io-card] h6').contains('Outputs')
 -                    .parents('[data-cy=process-io-card]').within((ctx) => {
++                cy.get("[data-cy=process-io-card] h6")
++                    .contains("Outputs")
++                    .parents("[data-cy=process-io-card]")
++                    .within(ctx => {
+                         cy.get(ctx).scrollIntoView();
 -                        cy.get('[data-cy="io-preview-image-toggle"]').click({waitForAnimations: false});
++                        cy.get('[data-cy="io-preview-image-toggle"]').click({ waitForAnimations: false });
+                         const outPdh = testOutputCollection.portable_data_hash;
+ 
 -                        verifyIOParameter('output_file', null, "Label Description", 'cat.png', `${outPdh}`);
 -                        verifyIOParameterImage('output_file', `/c=${outPdh}/cat.png`);
 -                        verifyIOParameter('output_file_with_secondary', null, "Doc Description", 'main.dat', `${outPdh}`);
 -                        verifyIOParameter('output_file_with_secondary', null, "Doc Description", 'secondary.dat', undefined, true);
 -                        verifyIOParameter('output_file_with_secondary', null, "Doc Description", 'secondary2.dat', undefined, true);
 -                        verifyIOParameter('output_dir', null, "Doc desc 1, Doc desc 2", 'outdir1', `${outPdh}`);
 -                        verifyIOParameter('output_bool', null, null, 'true');
 -                        verifyIOParameter('output_int', null, null, '1');
 -                        verifyIOParameter('output_long', null, null, '1');
 -                        verifyIOParameter('output_float', null, null, '100.5');
 -                        verifyIOParameter('output_double', null, null, '100.3');
 -                        verifyIOParameter('output_string', null, null, 'Hello output');
 -                        verifyIOParameter('output_file_array', null, null, 'output2.tar', `${outPdh}`);
 -                        verifyIOParameter('output_file_array', null, null, 'output3.tar', undefined, true);
 -                        verifyIOParameter('output_dir_array', null, null, 'outdir2', `${outPdh}`);
 -                        verifyIOParameter('output_dir_array', null, null, 'outdir3', undefined, true);
 -                        verifyIOParameter('output_int_array', null, null, ["10", "11", "12"]);
 -                        verifyIOParameter('output_long_array', null, null, ["51", "52"]);
 -                        verifyIOParameter('output_float_array', null, null, ["100.2", "100.4", "100.6"]);
 -                        verifyIOParameter('output_double_array', null, null, ["100.1", "100.2", "100.3"]);
 -                        verifyIOParameter('output_string_array', null, null, ["Hello", "Output", "!"]);
++                        verifyIOParameter("output_file", null, "Label Description", "cat.png", `${outPdh}`);
++                        verifyIOParameterImage("output_file", `/c=${outPdh}/cat.png`);
++                        verifyIOParameter("output_file_with_secondary", null, "Doc Description", "main.dat", `${outPdh}`);
++                        verifyIOParameter("output_file_with_secondary", null, "Doc Description", "secondary.dat", undefined, true);
++                        verifyIOParameter("output_file_with_secondary", null, "Doc Description", "secondary2.dat", undefined, true);
++                        verifyIOParameter("output_dir", null, "Doc desc 1, Doc desc 2", "outdir1", `${outPdh}`);
++                        verifyIOParameter("output_bool", null, null, "true");
++                        verifyIOParameter("output_int", null, null, "1");
++                        verifyIOParameter("output_long", null, null, "1");
++                        verifyIOParameter("output_float", null, null, "100.5");
++                        verifyIOParameter("output_double", null, null, "100.3");
++                        verifyIOParameter("output_string", null, null, "Hello output");
++                        verifyIOParameter("output_file_array", null, null, "output2.tar", `${outPdh}`);
++                        verifyIOParameter("output_file_array", null, null, "output3.tar", undefined, true);
++                        verifyIOParameter("output_dir_array", null, null, "outdir2", `${outPdh}`);
++                        verifyIOParameter("output_dir_array", null, null, "outdir3", undefined, true);
++                        verifyIOParameter("output_int_array", null, null, ["10", "11", "12"]);
++                        verifyIOParameter("output_long_array", null, null, ["51", "52"]);
++                        verifyIOParameter("output_float_array", null, null, ["100.2", "100.4", "100.6"]);
++                        verifyIOParameter("output_double_array", null, null, ["100.1", "100.2", "100.3"]);
++                        verifyIOParameter("output_string_array", null, null, ["Hello", "Output", "!"]);
+                     });
+             });
          });
  
-         // Get updated collection pdh
-         cy.getAll("@testOutputCollection").then(([testOutputCollection]) => {
 -        it('displays IO parameters with no value', function() {
 -
 -            const fakeOutputUUID = 'zzzzz-4zz18-abcdefghijklmno';
 -            const fakeOutputPDH = '11111111111111111111111111111111+99/';
++        it("displays IO parameters with no value", function () {
++            const fakeOutputUUID = "zzzzz-4zz18-abcdefghijklmno";
++            const fakeOutputPDH = "11111111111111111111111111111111+99/";
+ 
+             cy.loginAs(activeUser);
+ 
              // Add output uuid and inputs to container request
 -            cy.intercept({method: 'GET', url: '**/arvados/v1/container_requests/*'}, (req) => {
 -                req.reply((res) => {
 +            cy.intercept({ method: "GET", url: "**/arvados/v1/container_requests/*" }, req => {
 +                req.reply(res => {
-                     res.body.output_uuid = testOutputCollection.uuid;
+                     res.body.output_uuid = fakeOutputUUID;
                      res.body.mounts["/var/lib/cwl/cwl.input.json"] = {
-                         content: testInputs.map(param => param.input).reduce((acc, val) => Object.assign(acc, val), {}),
 -                        content: {}
++                        content: {},
                      };
                      res.body.mounts["/var/lib/cwl/workflow.json"] = {
                          content: {
@@@ -937,387 -1439,54 +1368,62 @@@
              });
  
              // Stub fake output collection
 -            cy.intercept({method: 'GET', url: `**/arvados/v1/collections/${fakeOutputUUID}*`}, {
 -                statusCode: 200,
 -                body: {
 -                    uuid: fakeOutputUUID,
 -                    portable_data_hash: fakeOutputPDH,
 +            cy.intercept(
-                 { method: "GET", url: `**/arvados/v1/collections/${testOutputCollection.uuid}*` },
++                { method: "GET", url: `**/arvados/v1/collections/${fakeOutputUUID}*` },
 +                {
 +                    statusCode: 200,
 +                    body: {
-                         uuid: testOutputCollection.uuid,
-                         portable_data_hash: testOutputCollection.portable_data_hash,
++                        uuid: fakeOutputUUID,
++                        portable_data_hash: fakeOutputPDH,
 +                    },
                  }
 -            });
 +            );
  
              // Stub fake output json
 -            cy.intercept({method: 'GET', url: `**/c%3D${fakeOutputUUID}/cwl.output.json`}, {
 -                statusCode: 200,
 -                body: {}
 -            });
 +            cy.intercept(
-                 { method: "GET", url: "**/c%3Dzzzzz-4zz18-zzzzzzzzzzzzzzz/cwl.output.json" },
++                { method: "GET", url: `**/c%3D${fakeOutputUUID}/cwl.output.json` },
 +                {
 +                    statusCode: 200,
-                     body: testOutputs.map(param => param.output).reduce((acc, val) => Object.assign(acc, val), {}),
-                 }
-             );
- 
-             // Stub webdav response, points to output json
-             cy.intercept(
-                 { method: "PROPFIND", url: "*" },
-                 {
-                     fixture: "webdav-propfind-outputs.xml",
++                    body: {},
 +                }
 +            );
-         });
- 
-         createContainerRequest(activeUser, "test_container_request", "arvados/jobs", ["echo", "hello world"], false, "Committed").as(
-             "containerRequest"
-         );
  
-         cy.getAll("@containerRequest", "@testOutputCollection").then(function ([containerRequest, testOutputCollection]) {
-             cy.goToPath(`/processes/${containerRequest.uuid}`);
-             cy.get("[data-cy=process-io-card] h6")
-                 .contains("Inputs")
-                 .parents("[data-cy=process-io-card]")
-                 .within(() => {
-                     verifyIOParameter("input_file", null, "Label Description", "input1.tar", "00000000000000000000000000000000+01");
-                     verifyIOParameter("input_file", null, "Label Description", "input1-2.txt", undefined, true);
-                     verifyIOParameter("input_file", null, "Label Description", "input1-3.txt", undefined, true);
-                     verifyIOParameter("input_file", null, "Label Description", "input1-4.txt", undefined, true);
-                     verifyIOParameter("input_dir", null, "Doc Description", "/", "11111111111111111111111111111111+01");
-                     verifyIOParameter("input_bool", null, "Doc desc 1, Doc desc 2", "true");
-                     verifyIOParameter("input_int", null, null, "1");
-                     verifyIOParameter("input_long", null, null, "1");
-                     verifyIOParameter("input_float", null, null, "1.5");
-                     verifyIOParameter("input_double", null, null, "1.3");
-                     verifyIOParameter("input_string", null, null, "Hello World");
-                     verifyIOParameter("input_file_array", null, null, "input2.tar", "00000000000000000000000000000000+02");
-                     verifyIOParameter("input_file_array", null, null, "input3.tar", undefined, true);
-                     verifyIOParameter("input_file_array", null, null, "input3-2.txt", undefined, true);
-                     verifyIOParameter("input_file_array", null, null, "Cannot display value", undefined, true);
-                     verifyIOParameter("input_dir_array", null, null, "/", "11111111111111111111111111111111+02");
-                     verifyIOParameter("input_dir_array", null, null, "/", "11111111111111111111111111111111+03", true);
-                     verifyIOParameter("input_dir_array", null, null, "Cannot display value", undefined, true);
-                     verifyIOParameter("input_int_array", null, null, ["1", "3", "5", "Cannot display value"]);
-                     verifyIOParameter("input_long_array", null, null, ["10", "20", "Cannot display value"]);
-                     verifyIOParameter("input_float_array", null, null, ["10.2", "10.4", "10.6", "Cannot display value"]);
-                     verifyIOParameter("input_double_array", null, null, ["20.1", "20.2", "20.3", "Cannot display value"]);
-                     verifyIOParameter("input_string_array", null, null, ["Hello", "World", "!", "Cannot display value"]);
-                     verifyIOParameter("input_bool_include", null, null, "Cannot display value");
-                     verifyIOParameter("input_int_include", null, null, "Cannot display value");
-                     verifyIOParameter("input_float_include", null, null, "Cannot display value");
-                     verifyIOParameter("input_string_include", null, null, "Cannot display value");
-                     verifyIOParameter("input_file_include", null, null, "Cannot display value");
-                     verifyIOParameter("input_directory_include", null, null, "Cannot display value");
-                     verifyIOParameter("input_file_url", null, null, "http://example.com/index.html");
-                 });
-             cy.get("[data-cy=process-io-card] h6")
-                 .contains("Outputs")
-                 .parents("[data-cy=process-io-card]")
-                 .within(ctx => {
-                     cy.get(ctx).scrollIntoView();
-                     cy.get('[data-cy="io-preview-image-toggle"]').click({ waitForAnimations: false });
-                     const outPdh = testOutputCollection.portable_data_hash;
-                     verifyIOParameter("output_file", null, "Label Description", "cat.png", `${outPdh}`);
-                     verifyIOParameterImage("output_file", `/c=${outPdh}/cat.png`);
-                     verifyIOParameter("output_file_with_secondary", null, "Doc Description", "main.dat", `${outPdh}`);
-                     verifyIOParameter("output_file_with_secondary", null, "Doc Description", "secondary.dat", undefined, true);
-                     verifyIOParameter("output_file_with_secondary", null, "Doc Description", "secondary2.dat", undefined, true);
-                     verifyIOParameter("output_dir", null, "Doc desc 1, Doc desc 2", "outdir1", `${outPdh}`);
-                     verifyIOParameter("output_bool", null, null, "true");
-                     verifyIOParameter("output_int", null, null, "1");
-                     verifyIOParameter("output_long", null, null, "1");
-                     verifyIOParameter("output_float", null, null, "100.5");
-                     verifyIOParameter("output_double", null, null, "100.3");
-                     verifyIOParameter("output_string", null, null, "Hello output");
-                     verifyIOParameter("output_file_array", null, null, "output2.tar", `${outPdh}`);
-                     verifyIOParameter("output_file_array", null, null, "output3.tar", undefined, true);
-                     verifyIOParameter("output_dir_array", null, null, "outdir2", `${outPdh}`);
-                     verifyIOParameter("output_dir_array", null, null, "outdir3", undefined, true);
-                     verifyIOParameter("output_int_array", null, null, ["10", "11", "12"]);
-                     verifyIOParameter("output_long_array", null, null, ["51", "52"]);
-                     verifyIOParameter("output_float_array", null, null, ["100.2", "100.4", "100.6"]);
-                     verifyIOParameter("output_double_array", null, null, ["100.1", "100.2", "100.3"]);
-                     verifyIOParameter("output_string_array", null, null, ["Hello", "Output", "!"]);
-                 });
-         });
-     });
- 
-     it("displays IO parameters with no value", function () {
-         const fakeOutputUUID = "zzzzz-4zz18-abcdefghijklmno";
-         const fakeOutputPDH = "11111111111111111111111111111111+99/";
- 
-         cy.loginAs(activeUser);
- 
-         // Add output uuid and inputs to container request
-         cy.intercept({ method: "GET", url: "**/arvados/v1/container_requests/*" }, req => {
-             req.reply(res => {
-                 res.body.output_uuid = fakeOutputUUID;
-                 res.body.mounts["/var/lib/cwl/cwl.input.json"] = {
-                     content: {},
-                 };
-                 res.body.mounts["/var/lib/cwl/workflow.json"] = {
-                     content: {
-                         $graph: [
-                             {
-                                 id: "#main",
-                                 inputs: testInputs.map(input => input.definition),
-                                 outputs: testOutputs.map(output => output.definition),
-                             },
-                         ],
-                     },
-                 };
 -            cy.readFile('cypress/fixtures/webdav-propfind-outputs.xml').then((data) => {
++            cy.readFile("cypress/fixtures/webdav-propfind-outputs.xml").then(data => {
+                 // Stub webdav response, points to output json
 -                cy.intercept({method: 'PROPFIND', url: '*'}, {
 -                    statusCode: 200,
 -                    body: data.replace(/zzzzz-4zz18-zzzzzzzzzzzzzzz/g, fakeOutputUUID)
 -                });
++                cy.intercept(
++                    { method: "PROPFIND", url: "*" },
++                    {
++                        statusCode: 200,
++                        body: data.replace(/zzzzz-4zz18-zzzzzzzzzzzzzzz/g, fakeOutputUUID),
++                    }
++                );
              });
-         });
- 
-         // Stub fake output collection
-         cy.intercept(
-             { method: "GET", url: `**/arvados/v1/collections/${fakeOutputUUID}*` },
-             {
-                 statusCode: 200,
-                 body: {
-                     uuid: fakeOutputUUID,
-                     portable_data_hash: fakeOutputPDH,
-                 },
-             }
-         );
  
-         // Stub fake output json
-         cy.intercept(
-             { method: "GET", url: `**/c%3D${fakeOutputUUID}/cwl.output.json` },
-             {
-                 statusCode: 200,
-                 body: {},
-             }
-         );
- 
-         cy.readFile("cypress/fixtures/webdav-propfind-outputs.xml").then(data => {
-             // Stub webdav response, points to output json
-             cy.intercept(
-                 { method: "PROPFIND", url: "*" },
-                 {
-                     statusCode: 200,
-                     body: data.replace(/zzzzz-4zz18-zzzzzzzzzzzzzzz/g, fakeOutputUUID),
-                 }
 -            createContainerRequest(
 -                activeUser,
 -                'test_container_request',
 -                'arvados/jobs',
 -                ['echo', 'hello world'],
 -                false, 'Committed')
 -            .as('containerRequest');
++            createContainerRequest(activeUser, "test_container_request", "arvados/jobs", ["echo", "hello world"], false, "Committed").as(
++                "containerRequest"
 +            );
-         });
  
-         createContainerRequest(activeUser, "test_container_request", "arvados/jobs", ["echo", "hello world"], false, "Committed").as(
-             "containerRequest"
-         );
- 
-         cy.getAll("@containerRequest").then(function ([containerRequest]) {
-             cy.goToPath(`/processes/${containerRequest.uuid}`);
-             cy.get("[data-cy=process-io-card] h6")
-                 .contains("Inputs")
-                 .parents("[data-cy=process-io-card]")
-                 .within(() => {
-                     cy.wait(2000);
-                     cy.waitForDom();
-                     cy.get("tbody tr").each(item => {
-                         cy.wrap(item).contains("No value");
 -            cy.getAll('@containerRequest').then(function([containerRequest]) {
++            cy.getAll("@containerRequest").then(function ([containerRequest]) {
+                 cy.goToPath(`/processes/${containerRequest.uuid}`);
 -                cy.get('[data-cy=process-io-card] h6').contains('Inputs')
 -                    .parents('[data-cy=process-io-card]').within(() => {
++                cy.get("[data-cy=process-io-card] h6")
++                    .contains("Inputs")
++                    .parents("[data-cy=process-io-card]")
++                    .within(() => {
+                         cy.wait(2000);
+                         cy.waitForDom();
 -                        cy.get('tbody tr').each((item) => {
 -                            cy.wrap(item).contains('No value');
++                        cy.get("tbody tr").each(item => {
++                            cy.wrap(item).contains("No value");
+                         });
                      });
-                 });
-             cy.get("[data-cy=process-io-card] h6")
-                 .contains("Outputs")
-                 .parents("[data-cy=process-io-card]")
-                 .within(() => {
-                     cy.get("tbody tr").each(item => {
-                         cy.wrap(item).contains("No value");
 -                cy.get('[data-cy=process-io-card] h6').contains('Outputs')
 -                    .parents('[data-cy=process-io-card]').within(() => {
 -                        cy.get('tbody tr').each((item) => {
 -                            cy.wrap(item).contains('No value');
++                cy.get("[data-cy=process-io-card] h6")
++                    .contains("Outputs")
++                    .parents("[data-cy=process-io-card]")
++                    .within(() => {
++                        cy.get("tbody tr").each(item => {
++                            cy.wrap(item).contains("No value");
+                         });
                      });
-                 });
-         });
-     });
- 
-     it("allows copying processes", function () {
-         const crName = "first_container_request";
-         const copiedCrName = "copied_container_request";
-         createContainerRequest(activeUser, crName, "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (containerRequest) {
-             cy.loginAs(activeUser);
-             cy.goToPath(`/processes/${containerRequest.uuid}`);
-             cy.get("[data-cy=process-details]").should("contain", crName);
- 
-             cy.get("[data-cy=process-details]").find('button[title="More options"]').click();
-             cy.get("ul[data-cy=context-menu]").contains("Copy and re-run process").click();
-         });
- 
-         cy.get("[data-cy=form-dialog]").within(() => {
-             cy.get("input[name=name]").clear().type(copiedCrName);
-             cy.get("[data-cy=projects-tree-home-tree-picker]").click();
-             cy.get("[data-cy=form-submit-btn]").click();
-         });
- 
-         cy.get("[data-cy=process-details]").should("contain", copiedCrName);
-         cy.get("[data-cy=process-details]").find("button").contains("Run");
-     });
- 
-     const getFakeContainer = fakeContainerUuid => ({
-         href: `/containers/${fakeContainerUuid}`,
-         kind: "arvados#container",
-         etag: "ecfosljpnxfari9a8m7e4yv06",
-         uuid: fakeContainerUuid,
-         owner_uuid: "zzzzz-tpzed-000000000000000",
-         created_at: "2023-02-13T15:55:47.308915000Z",
-         modified_by_client_uuid: "zzzzz-ozdt8-q6dzdi1lcc03155",
-         modified_by_user_uuid: "zzzzz-tpzed-000000000000000",
-         modified_at: "2023-02-15T19:12:45.987086000Z",
-         command: [
-             "arvados-cwl-runner",
-             "--api=containers",
-             "--local",
-             "--project-uuid=zzzzz-j7d0g-yr18k784zplfeza",
-             "/var/lib/cwl/workflow.json#main",
-             "/var/lib/cwl/cwl.input.json",
-         ],
-         container_image: "4ad7d11381df349e464694762db14e04+303",
-         cwd: "/var/spool/cwl",
-         environment: {},
-         exit_code: null,
-         finished_at: null,
-         locked_by_uuid: null,
-         log: null,
-         output: null,
-         output_path: "/var/spool/cwl",
-         progress: null,
-         runtime_constraints: {
-             API: true,
-             cuda: {
-                 device_count: 0,
-                 driver_version: "",
-                 hardware_capability: "",
-             },
-             keep_cache_disk: 2147483648,
-             keep_cache_ram: 0,
-             ram: 1342177280,
-             vcpus: 1,
-         },
-         runtime_status: {},
-         started_at: null,
-         auth_uuid: null,
-         scheduling_parameters: {
-             max_run_time: 0,
-             partitions: [],
-             preemptible: false,
-         },
-         runtime_user_uuid: "zzzzz-tpzed-vllbpebicy84rd5",
-         runtime_auth_scopes: ["all"],
-         lock_count: 2,
-         gateway_address: null,
-         interactive_session_started: false,
-         output_storage_classes: ["default"],
-         output_properties: {},
-         cost: 0.0,
-         subrequests_cost: 0.0,
-     });
- 
-     it("shows cancel button when appropriate", function () {
-         // Ignore collection requests
-         cy.intercept(
-             { method: "GET", url: `**/arvados/v1/collections/*` },
-             {
-                 statusCode: 200,
-                 body: {},
-             }
-         );
- 
-         // Uncommitted container
-         const crUncommitted = `Test process ${Math.floor(Math.random() * 999999)}`;
-         createContainerRequest(activeUser, crUncommitted, "arvados/jobs", ["echo", "hello world"], false, "Uncommitted").then(function (
-             containerRequest
-         ) {
-             // Navigate to process and verify run / cancel button
-             cy.goToPath(`/processes/${containerRequest.uuid}`);
-             cy.waitForDom();
-             cy.get("[data-cy=process-details]").should("contain", crUncommitted);
-             cy.get("[data-cy=process-run-button]").should("exist");
-             cy.get("[data-cy=process-cancel-button]").should("not.exist");
-         });
- 
-         // Queued container
-         const crQueued = `Test process ${Math.floor(Math.random() * 999999)}`;
-         const fakeCrUuid = "zzzzz-dz642-000000000000001";
-         createContainerRequest(activeUser, crQueued, "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (containerRequest) {
-             // Fake container uuid
-             cy.intercept({ method: "GET", url: `**/arvados/v1/container_requests/${containerRequest.uuid}` }, req => {
-                 req.reply(res => {
-                     res.body.output_uuid = fakeCrUuid;
-                     res.body.priority = 500;
-                     res.body.state = "Committed";
-                 });
              });
- 
-             // Fake container
-             const container = getFakeContainer(fakeCrUuid);
-             cy.intercept(
-                 { method: "GET", url: `**/arvados/v1/container/${fakeCrUuid}` },
-                 {
-                     statusCode: 200,
-                     body: { ...container, state: "Queued", priority: 500 },
-                 }
-             );
- 
-             // Navigate to process and verify cancel button
-             cy.goToPath(`/processes/${containerRequest.uuid}`);
-             cy.waitForDom();
-             cy.get("[data-cy=process-details]").should("contain", crQueued);
-             cy.get("[data-cy=process-cancel-button]").contains("Cancel");
-         });
- 
-         // Locked container
-         const crLocked = `Test process ${Math.floor(Math.random() * 999999)}`;
-         const fakeCrLockedUuid = "zzzzz-dz642-000000000000002";
-         createContainerRequest(activeUser, crLocked, "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (containerRequest) {
-             // Fake container uuid
-             cy.intercept({ method: "GET", url: `**/arvados/v1/container_requests/${containerRequest.uuid}` }, req => {
-                 req.reply(res => {
-                     res.body.output_uuid = fakeCrLockedUuid;
-                     res.body.priority = 500;
-                     res.body.state = "Committed";
-                 });
-             });
- 
-             // Fake container
-             const container = getFakeContainer(fakeCrLockedUuid);
-             cy.intercept(
-                 { method: "GET", url: `**/arvados/v1/container/${fakeCrLockedUuid}` },
-                 {
-                     statusCode: 200,
-                     body: { ...container, state: "Locked", priority: 500 },
-                 }
-             );
- 
-             // Navigate to process and verify cancel button
-             cy.goToPath(`/processes/${containerRequest.uuid}`);
-             cy.waitForDom();
-             cy.get("[data-cy=process-details]").should("contain", crLocked);
-             cy.get("[data-cy=process-cancel-button]").contains("Cancel");
-         });
- 
-         // On Hold container
-         const crOnHold = `Test process ${Math.floor(Math.random() * 999999)}`;
-         const fakeCrOnHoldUuid = "zzzzz-dz642-000000000000003";
-         createContainerRequest(activeUser, crOnHold, "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (containerRequest) {
-             // Fake container uuid
-             cy.intercept({ method: "GET", url: `**/arvados/v1/container_requests/${containerRequest.uuid}` }, req => {
-                 req.reply(res => {
-                     res.body.output_uuid = fakeCrOnHoldUuid;
-                     res.body.priority = 0;
-                     res.body.state = "Committed";
-                 });
-             });
- 
-             // Fake container
-             const container = getFakeContainer(fakeCrOnHoldUuid);
-             cy.intercept(
-                 { method: "GET", url: `**/arvados/v1/container/${fakeCrOnHoldUuid}` },
-                 {
-                     statusCode: 200,
-                     body: { ...container, state: "Queued", priority: 0 },
-                 }
-             );
- 
-             // Navigate to process and verify cancel button
-             cy.goToPath(`/processes/${containerRequest.uuid}`);
-             cy.waitForDom();
-             cy.get("[data-cy=process-details]").should("contain", crOnHold);
-             cy.get("[data-cy=process-run-button]").should("exist");
-             cy.get("[data-cy=process-cancel-button]").should("not.exist");
          });
      });
 -
  });
diff --cc cypress/integration/project.spec.js
index f0de6f86,fd14cc42..74c4cd91
--- a/cypress/integration/project.spec.js
+++ b/cypress/integration/project.spec.js
@@@ -126,24 -125,27 +126,25 @@@ describe("Project tests", function () 
          cy.loginAs(activeUser);
  
          // Create project
 -        cy.get('[data-cy=side-panel-button]').click();
 -        cy.get('[data-cy=side-panel-new-project]').click();
 -        cy.get('[data-cy=form-dialog]')
 -            .should('contain', 'New Project')
 +        cy.get("[data-cy=side-panel-button]").click();
 +        cy.get("[data-cy=side-panel-new-project]").click();
 +        cy.get("[data-cy=form-dialog]")
 +            .should("contain", "New Project")
              .within(() => {
 -                cy.get('[data-cy=name-field]').within(() => {
 -                    cy.get('input').type(projName);
 +                cy.get("[data-cy=name-field]").within(() => {
 +                    cy.get("input").type(projName);
                  });
              });
 -        cy.get('[data-cy=form-submit-btn]').click();
 -        cy.get('[data-cy=form-dialog]').should('not.exist');
 +        cy.get("[data-cy=form-submit-btn]").click();
++        cy.get("[data-cy=form-dialog]").should("not.exist");
  
          const editProjectDescription = (name, type) => {
 -            cy.get('[data-cy=side-panel-tree]').contains('Home Projects').click();
 -            cy.get('[data-cy=project-panel] tbody tr').contains(name).rightclick();
 -            cy.get('[data-cy=context-menu]').contains('Edit').click();
 -            cy.get('[data-cy=form-dialog]').within(() => {
 -                cy.get('div[contenteditable=true]')
 -                    .click()
 -                    .type(type);
 -                cy.get('[data-cy=form-submit-btn]').click();
 +            cy.get("[data-cy=side-panel-tree]").contains("Home Projects").click();
 +            cy.get("[data-cy=project-panel] tbody tr").contains(name).rightclick({ force: true });
 +            cy.get("[data-cy=context-menu]").contains("Edit").click();
 +            cy.get("[data-cy=form-dialog]").within(() => {
 +                cy.get("div[contenteditable=true]").click().type(type);
 +                cy.get("[data-cy=form-submit-btn]").click();
              });
          };
  
@@@ -539,28 -535,32 +540,31 @@@
          const projectName = `Test project (${Math.floor(999999 * Math.random())})`;
  
          cy.loginAs(activeUser);
 -        cy.get('[data-cy=side-panel-button]').click();
 -        cy.get('[data-cy=side-panel-new-project]').click();
 -        cy.get('[data-cy=form-dialog]')
 -            .should('contain', 'New Project')
 +        cy.get("[data-cy=side-panel-button]").click();
 +        cy.get("[data-cy=side-panel-new-project]").click();
 +        cy.get("[data-cy=form-dialog]")
 +            .should("contain", "New Project")
              .within(() => {
 -                cy.get('[data-cy=name-field]').within(() => {
 -                    cy.get('input').type(projectName);
 +                cy.get("[data-cy=name-field]").within(() => {
 +                    cy.get("input").type(projectName);
                  });
 -                cy.get('[data-cy=form-submit-btn]').click();
 +                cy.get("[data-cy=form-submit-btn]").click();
              });
 -        cy.get('[data-cy=form-dialog]').should("not.exist");
 -        cy.get('[data-cy=snackbar]').contains('created');
 -        cy.get('[data-cy=snackbar]').should("not.exist");
 -        cy.get('[data-cy=side-panel-tree]').contains('Projects').click();
 +        cy.get("[data-cy=form-dialog]").should("not.exist");
++        cy.get("[data-cy=snackbar]").contains("created");
++        cy.get("[data-cy=snackbar]").should("not.exist");
 +        cy.get("[data-cy=side-panel-tree]").contains("Projects").click();
+         cy.waitForDom();
 -        cy.get('[data-cy=project-panel]').contains(projectName).should('be.visible').rightclick();
 -        cy.get('[data-cy=context-menu]').contains('Copy to clipboard').click();
 -        cy.window().then((win) => (
 -            win.navigator.clipboard.readText().then((text) => {
 -                expect(text).to.match(/https\:\/\/127\.0\.0\.1\:[0-9]+\/projects\/[a-z0-9]{5}-[a-z0-9]{5}-[a-z0-9]{15}/,);
 +        cy.get("[data-cy=project-panel]").contains(projectName).should("be.visible").rightclick();
 +        cy.get("[data-cy=context-menu]").contains("Copy to clipboard").click();
 +        cy.window().then(win =>
 +            win.navigator.clipboard.readText().then(text => {
-                 expect(text).to.match(/https\:\/\/localhost\:[0-9]+\/projects\/[a-z0-9]{5}-[a-z0-9]{5}-[a-z0-9]{15}/);
++                expect(text).to.match(/https\:\/\/127\.0\.0\.1\:[0-9]+\/projects\/[a-z0-9]{5}-[a-z0-9]{5}-[a-z0-9]{15}/);
              })
 -        ));
 -
 +        );
      });
  
 -    it('sorts displayed items correctly', () => {
 +    it("sorts displayed items correctly", () => {
          cy.loginAs(activeUser);
  
          cy.get('[data-cy=project-panel] button[title="Select columns"]').click();
diff --cc cypress/integration/search.spec.js
index bfcca0c6,085298dc..d8aa35d3
--- a/cypress/integration/search.spec.js
+++ b/cypress/integration/search.spec.js
@@@ -165,23 -160,23 +165,23 @@@ describe("Search tests", function () 
          });
      });
  
 -    it('shows search context menu', function() {
 +    it("shows search context menu", function () {
-         const colName = `Collection ${Math.floor(Math.random() * Math.floor(999999))}`;
-         const federatedColName = `Collection ${Math.floor(Math.random() * Math.floor(999999))}`;
+         const colName = `Home Collection ${Math.floor(Math.random() * Math.floor(999999))}`;
+         const federatedColName = `Federated Collection ${Math.floor(Math.random() * Math.floor(999999))}`;
          const federatedColUuid = "xxxxx-4zz18-000000000000000";
  
          // Intercept config to insert remote cluster
-         cy.intercept({ method: "GET", hostname: "localhost", url: "**/arvados/v1/config?nocache=*" }, req => {
 -        cy.intercept({method: 'GET', hostname: '127.0.0.1', url: '**/arvados/v1/config?nocache=*'}, (req) => {
 -            req.reply((res) => {
++        cy.intercept({ method: "GET", hostname: "127.0.0.1", url: "**/arvados/v1/config?nocache=*" }, req => {
 +            req.reply(res => {
                  res.body.RemoteClusters = {
                      "*": res.body.RemoteClusters["*"],
 -                    "xxxxx": {
 -                        "ActivateUsers": true,
 -                        "Host": "xxxxx.fakecluster.tld",
 -                        "Insecure": false,
 -                        "Proxy": true,
 -                        "Scheme": ""
 -                    }
 +                    xxxxx: {
 +                        ActivateUsers: true,
 +                        Host: "xxxxx.fakecluster.tld",
 +                        Insecure: false,
 +                        Proxy: true,
 +                        Scheme: "",
 +                    },
                  };
              });
          });
@@@ -275,46 -270,51 +275,50 @@@
              });
  
              // Check copy to clipboard
 -            cy.get('[data-cy=search-results]').contains(colName).rightclick();
 -            cy.get('[data-cy=context-menu]').within((ctx) => {
 +            cy.get("[data-cy=search-results]").contains(colName).rightclick();
 +            cy.get("[data-cy=context-menu]").within(ctx => {
                  // Check that there are 4 items in the menu
 -                cy.get(ctx).children().should('have.length', 4);
 -                cy.contains('API Details');
 -                cy.contains('Copy to clipboard');
 -                cy.contains('Open in new tab');
 -                cy.contains('View details');
 +                cy.get(ctx).children().should("have.length", 4);
 +                cy.contains("API Details");
 +                cy.contains("Copy to clipboard");
 +                cy.contains("Open in new tab");
 +                cy.contains("View details");
  
 -                cy.contains('Copy to clipboard').click();
 +                cy.contains("Copy to clipboard").click();
+                 cy.waitForDom();
 -                cy.window().then((win) => (
 -                    win.navigator.clipboard.readText().then((text) => {
 +                cy.window().then(win =>
 +                    win.navigator.clipboard.readText().then(text => {
                          expect(text).to.match(new RegExp(`/collections/${testCollection.uuid}$`));
                      })
 -                ));
 +                );
              });
  
              // Check open in new tab
 -            cy.get('[data-cy=search-results]').contains(colName).rightclick();
 -            cy.get('[data-cy=context-menu]').within(() => {
 -                cy.contains('Open in new tab').click();
 +            cy.get("[data-cy=search-results]").contains(colName).rightclick();
 +            cy.get("[data-cy=context-menu]").within(() => {
 +                cy.contains("Open in new tab").click();
+                 cy.waitForDom();
 -                cy.get('@Open').should('have.been.calledOnceWith', `${window.location.origin}/collections/${testCollection.uuid}`)
 +                cy.get("@Open").should("have.been.calledOnceWith", `${window.location.origin}/collections/${testCollection.uuid}`);
              });
  
              // Check federated result copy to clipboard
 -            cy.get('[data-cy=search-results]').contains(federatedColName).rightclick();
 -            cy.get('[data-cy=context-menu]').within(() => {
 -                cy.contains('Copy to clipboard').click();
 +            cy.get("[data-cy=search-results]").contains(federatedColName).rightclick();
 +            cy.get("[data-cy=context-menu]").within(() => {
 +                cy.contains("Copy to clipboard").click();
+                 cy.waitForDom();
 -                cy.window().then((win) => (
 -                    win.navigator.clipboard.readText().then((text) => {
 +                cy.window().then(win =>
 +                    win.navigator.clipboard.readText().then(text => {
                          expect(text).to.equal(`https://wb2.xxxxx.fakecluster.tld/collections/${federatedColUuid}`);
                      })
 -                ));
 +                );
              });
              // Check open in new tab
 -            cy.get('[data-cy=search-results]').contains(federatedColName).rightclick();
 -            cy.get('[data-cy=context-menu]').within(() => {
 -                cy.contains('Open in new tab').click();
 +            cy.get("[data-cy=search-results]").contains(federatedColName).rightclick();
 +            cy.get("[data-cy=context-menu]").within(() => {
 +                cy.contains("Open in new tab").click();
+                 cy.waitForDom();
 -                cy.get('@Open').should('have.been.calledWith', `https://wb2.xxxxx.fakecluster.tld/collections/${federatedColUuid}`)
 +                cy.get("@Open").should("have.been.calledWith", `https://wb2.xxxxx.fakecluster.tld/collections/${federatedColUuid}`);
              });
 -
          });
      });
  });
diff --cc cypress/integration/virtual-machine-admin.spec.js
index f6a50877,80d64977..92011b20
--- a/cypress/integration/virtual-machine-admin.spec.js
+++ b/cypress/integration/virtual-machine-admin.spec.js
@@@ -40,53 -40,52 +40,53 @@@ describe("Virtual machine login manage 
              .within(() => {
                  cy.get('button[title="Add Login Permission"]').click();
              });
 -        cy.get('[data-cy=form-dialog]')
 -            .should('contain', 'Add login permission')
 +        cy.get("[data-cy=form-dialog]")
 +            .should("contain", "Add login permission")
              .within(() => {
 -                cy.get('label')
 -                  .contains('Search for user')
 -                  .parent()
 -                  .within(() => {
 -                    cy.get('input').type('VMAdmin');
 -                  })
 +                cy.get("label")
 +                    .contains("Search for user")
 +                    .parent()
 +                    .within(() => {
 +                        cy.get("input").type("VMAdmin");
 +                    });
              });
-         cy.get("[role=tooltip]").click();
 -        cy.waitForDom().get('[role=tooltip]').click();
 -        cy.get('[data-cy=form-dialog]').as('add-login-dialog')
 -            .should('contain', 'Add login permission')
++        cy.waitForDom().get("[role=tooltip]").click();
 +        cy.get("[data-cy=form-dialog]")
 +            .as("add-login-dialog")
 +            .should("contain", "Add login permission")
              .within(() => {
 -                cy.get('label')
 -                  .contains('Add groups')
 -                  .parent()
 -                  .within(() => {
 -                    cy.get('input').type('docker ');
 -                    // Veryfy submit enabled (form has changed)
 -                    cy.get('@add-login-dialog').within(() => {
 -                        cy.get('[data-cy=form-submit-btn]').should('be.enabled');
 -                    });
 -                    cy.get('input').type('sudo');
 -                    // Veryfy submit disabled (partial input in chips)
 -                    cy.get('@add-login-dialog').within(() => {
 -                        cy.get('[data-cy=form-submit-btn]').should('be.disabled');
 +                cy.get("label")
 +                    .contains("Add groups")
 +                    .parent()
 +                    .within(() => {
 +                        cy.get("input").type("docker ");
 +                        // Veryfy submit enabled (form has changed)
 +                        cy.get("@add-login-dialog").within(() => {
 +                            cy.get("[data-cy=form-submit-btn]").should("be.enabled");
 +                        });
 +                        cy.get("input").type("sudo");
 +                        // Veryfy submit disabled (partial input in chips)
 +                        cy.get("@add-login-dialog").within(() => {
 +                            cy.get("[data-cy=form-submit-btn]").should("be.disabled");
 +                        });
 +                        cy.get("input").type("{enter}");
                      });
 -                    cy.get('input').type('{enter}');
 -                  })
              });
 -        cy.get('[data-cy=form-dialog]').within(() => {
 -            cy.get('[data-cy=form-submit-btn]').click();
 +        cy.get("[data-cy=form-dialog]").within(() => {
 +            cy.get("[data-cy=form-submit-btn]").click();
          });
  
 -        cy.get('[data-cy=vm-admin-table]')
 +        cy.get("[data-cy=vm-admin-table]")
              .contains(vmHost)
 -            .parents('tr')
 +            .parents("tr")
              .within(() => {
 -                cy.get('td').contains('admin');
 -        });
 +                cy.get("td").contains("admin");
 +            });
  
          // Add login permission to activeUser
 -        cy.get('[data-cy=vm-admin-table]')
 +        cy.get("[data-cy=vm-admin-table]")
              .contains(vmHost)
 -            .parents('tr')
 +            .parents("tr")
              .within(() => {
                  cy.get('button[title="Add Login Permission"]').click();
              });
@@@ -224,35 -238,42 +224,36 @@@
          // Remove login permissions
          cy.loginAs(adminUser);
          cy.get('header button[title="Admin Panel"]').click();
 -        cy.get('#admin-menu').contains('Virtual Machines').click();
 +        cy.get("#admin-menu").contains("Virtual Machines").click();
  
 -        cy.get('[data-cy=vm-admin-table]')
 -            .contains('user'); // Wait for page to finish
 +        cy.get("[data-cy=vm-admin-table]").contains("user"); // Wait for page to finish
  
 -        cy.get('[data-cy=vm-admin-table]')
 +        cy.get("[data-cy=vm-admin-table]")
              .contains(vmHost)
 -            .parents('tr')
 -            .as('vmRow')
 -            .contains('user')
 -            .parents('[role=button]')
 -            .find('svg')
 -            .as('removeButton');
 -        cy.get('@removeButton').click();
 -        cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
 -
 -        cy.get('@vmRow')
 -            .within(() => {
 -                cy.get('div[role=button]').should('not.contain', 'user');
 -                cy.get('div[role=button]').should('have.length', 1)
 -            });
 +            .parents("tr")
 +            .as("vmRow")
 +            .contains("user")
 +            .parents("[role=button]")
 +            .find("svg")
 +            .as("removeButton");
 +        cy.get("@removeButton").click();
 +        cy.get("[data-cy=confirmation-dialog-ok-btn]").click();
 +
 +        cy.get("@vmRow").within(() => {
 +            cy.get("div[role=button]").should("not.contain", "user");
 +            cy.get("div[role=button]").should("have.length", 1);
 +        });
  
 -        cy.get('@vmRow')
 -            .find('div[role=button]')
 -            .contains('admin')
 -            .parents('[role=button]')
 -            .find('svg')
 -            .as('removeButton');
 -        cy.get('@removeButton').click();
 -        cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
 +        cy.get("@vmRow").find("div[role=button]").contains("admin").parents("[role=button]").find("svg").as("removeButton");
 +        cy.get("@removeButton").click();
 +        cy.get("[data-cy=confirmation-dialog-ok-btn]").click();
  
-         cy.get("[data-cy=vm-admin-table]")
 -        cy.waitForDom().get('[data-cy=vm-admin-table]')
++        cy.waitForDom()
++            .get("[data-cy=vm-admin-table]")
              .contains(vmHost)
 -            .parents('tr')
 +            .parents("tr")
              .within(() => {
 -                cy.get('div[role=button]').should('not.exist');
 +                cy.get("div[role=button]").should("not.exist");
              });
  
          // Check admin's vm page for login
diff --cc cypress/support/commands.js
index 199b5c13,67ddf45d..46d77fe8
--- a/cypress/support/commands.js
+++ b/cypress/support/commands.js
@@@ -28,17 -28,23 +28,23 @@@
  // -- This will overwrite an existing command --
  // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
  
+ import { extractFilesData } from "services/collection-service/collection-service-files-response";
+ 
 -const controllerURL = Cypress.env('controller_url');
 -const systemToken = Cypress.env('system_token');
 +const controllerURL = Cypress.env("controller_url");
 +const systemToken = Cypress.env("system_token");
  let createdResources = [];
  
- // Clean up on a 'before' hook to allow post-mortem analysis on individual tests.
- beforeEach(function () {
 -const containerLogFolderPrefix = 'log for container ';
++const containerLogFolderPrefix = "log for container ";
+ 
+ // Clean up anything that was created.  You can temporarily add
+ // 'return' to the top if you need the resources to hang around to
+ // debug a specific test.
+ afterEach(function () {
      if (createdResources.length === 0) {
          return;
      }
-     cy.log(`Cleaning ${createdResources.length} previously created resource(s)`);
+     cy.log(`Cleaning ${createdResources.length} previously created resource(s).`);
 -    createdResources.forEach(function({suffix, uuid}) {
 +    createdResources.forEach(function ({ suffix, uuid }) {
          // Don't fail when a resource isn't already there, some objects may have
          // been removed, directly or indirectly, from the test that created them.
          cy.deleteResource(systemToken, suffix, uuid, false);
@@@ -56,290 -77,374 +62,360 @@@ Cypress.Commands.add
              qs: auth ? qs : Object.assign({ api_token: token }, qs),
              auth: auth ? { bearer: `${token}` } : undefined,
              followRedirect: followRedirect,
 -            failOnStatusCode: failOnStatusCode
 +            failOnStatusCode: failOnStatusCode,
          });
 -    });
 -});
 +    }
 +);
  
 -Cypress.Commands.add(
 -    "getUser", (username, first_name = '', last_name = '', is_admin = false, is_active = true) => {
 -        // Create user if not already created
 -        return cy.doRequest('POST', '/auth/controller/callback', {
 -            auth_info: JSON.stringify({
 -                email: `${username}@example.local`,
 -                username: username,
 -                first_name: first_name,
 -                last_name: last_name,
 -                alternate_emails: []
 -            }),
 -            return_to: ',https://example.local'
 -        }, null, systemToken, true, false) // Don't follow redirects so we can catch the token
 -        .its('headers.location').as('location')
 -        // Get its token and set the account up as admin and/or active
 -        .then(function () {
 -            this.userToken = this.location.split("=")[1]
 -            assert.isString(this.userToken)
 -            return cy.doRequest('GET', '/arvados/v1/users', null, {
 -                filters: `[["username", "=", "${username}"]]`
 -            })
 -            .its('body.items.0').as('aUser')
 +Cypress.Commands.add("getUser", (username, first_name = "", last_name = "", is_admin = false, is_active = true) => {
 +    // Create user if not already created
 +    return (
 +        cy
 +            .doRequest(
 +                "POST",
 +                "/auth/controller/callback",
 +                {
 +                    auth_info: JSON.stringify({
 +                        email: `${username}@example.local`,
 +                        username: username,
 +                        first_name: first_name,
 +                        last_name: last_name,
 +                        alternate_emails: [],
 +                    }),
 +                    return_to: ",https://example.local",
 +                },
 +                null,
 +                systemToken,
 +                true,
 +                false
 +            ) // Don't follow redirects so we can catch the token
 +            .its("headers.location")
 +            .as("location")
 +            // Get its token and set the account up as admin and/or active
              .then(function () {
 -                cy.doRequest('PUT', `/arvados/v1/users/${this.aUser.uuid}`, {
 -                    user: {
 -                        is_admin: is_admin,
 -                        is_active: is_active
 -                    }
 -                })
 -                .its('body').as('theUser')
 -                .then(function () {
 -                    cy.doRequest('GET', '/arvados/v1/api_clients', null, {
 -                        filters: `[["is_trusted", "=", false]]`,
 -                        order: `["created_at desc"]`
 +                this.userToken = this.location.split("=")[1];
 +                assert.isString(this.userToken);
 +                return cy
 +                    .doRequest("GET", "/arvados/v1/users", null, {
 +                        filters: `[["username", "=", "${username}"]]`,
                      })
 -                    .its('body.items').as('apiClients')
 +                    .its("body.items.0")
 +                    .as("aUser")
                      .then(function () {
 -                        if (this.apiClients.length > 0) {
 -                            cy.doRequest('PUT', `/arvados/v1/api_clients/${this.apiClients[0].uuid}`, {
 -                                api_client: {
 -                                    is_trusted: true
 -                                }
 -                            })
 -                            .its('body').as('updatedApiClient')
 -                            .then(function() {
 -                                assert(this.updatedApiClient.is_trusted);
 -                            })
 -                        }
 -                    })
 -                    .then(function () {
 -                        return { user: this.theUser, token: this.userToken };
 -                    })
 -                })
 +                        cy.doRequest("PUT", `/arvados/v1/users/${this.aUser.uuid}`, {
 +                            user: {
 +                                is_admin: is_admin,
 +                                is_active: is_active,
 +                            },
 +                        })
 +                            .its("body")
 +                            .as("theUser")
 +                            .then(function () {
 +                                cy.doRequest("GET", "/arvados/v1/api_clients", null, {
 +                                    filters: `[["is_trusted", "=", false]]`,
 +                                    order: `["created_at desc"]`,
 +                                })
 +                                    .its("body.items")
 +                                    .as("apiClients")
 +                                    .then(function () {
 +                                        if (this.apiClients.length > 0) {
 +                                            cy.doRequest("PUT", `/arvados/v1/api_clients/${this.apiClients[0].uuid}`, {
 +                                                api_client: {
 +                                                    is_trusted: true,
 +                                                },
 +                                            })
 +                                                .its("body")
 +                                                .as("updatedApiClient")
 +                                                .then(function () {
 +                                                    assert(this.updatedApiClient.is_trusted);
 +                                                });
 +                                        }
 +                                    })
 +                                    .then(function () {
 +                                        return { user: this.theUser, token: this.userToken };
 +                                    });
 +                            });
 +                    });
              })
 -        })
 -    }
 -)
 +    );
 +});
  
 -Cypress.Commands.add(
 -    "createLink", (token, data) => {
 -        return cy.createResource(token, 'links', {
 -            link: JSON.stringify(data)
 -        })
 -    }
 -)
 +Cypress.Commands.add("createLink", (token, data) => {
 +    return cy.createResource(token, "links", {
 +        link: JSON.stringify(data),
 +    });
 +});
  
- Cypress.Commands.add("createGroup", (token, data) => {
-     return cy.createResource(token, "groups", {
-         group: JSON.stringify(data),
-         ensure_unique_name: true,
-     });
- });
- 
- Cypress.Commands.add("trashGroup", (token, uuid) => {
-     return cy.deleteResource(token, "groups", uuid);
- });
- 
- Cypress.Commands.add("createWorkflow", (token, data) => {
-     return cy.createResource(token, "workflows", {
-         workflow: JSON.stringify(data),
-         ensure_unique_name: true,
-     });
- });
- 
- Cypress.Commands.add("getCollection", (token, uuid) => {
-     return cy.getResource(token, "collections", uuid);
- });
- 
- Cypress.Commands.add("createCollection", (token, data) => {
-     return cy.createResource(token, "collections", {
-         collection: JSON.stringify(data),
-         ensure_unique_name: true,
-     });
- });
- 
- Cypress.Commands.add("updateCollection", (token, uuid, data) => {
-     return cy.updateResource(token, "collections", uuid, {
-         collection: JSON.stringify(data),
-     });
- });
- 
- Cypress.Commands.add("getContainer", (token, uuid) => {
-     return cy.getResource(token, "containers", uuid);
- });
- 
- Cypress.Commands.add("updateContainer", (token, uuid, data) => {
-     return cy.updateResource(token, "containers", uuid, {
-         container: JSON.stringify(data),
-     });
- });
- 
- Cypress.Commands.add("createContainerRequest", (token, data) => {
-     return cy.createResource(token, "container_requests", {
-         container_request: JSON.stringify(data),
-         ensure_unique_name: true,
-     });
- });
- 
- Cypress.Commands.add("updateContainerRequest", (token, uuid, data) => {
-     return cy.updateResource(token, "container_requests", uuid, {
-         container_request: JSON.stringify(data),
-     });
- });
- 
- Cypress.Commands.add("createLog", (token, data) => {
-     return cy.createResource(token, "logs", {
-         log: JSON.stringify(data),
-     });
- });
+ Cypress.Commands.add(
 -    "createGroup", (token, data) => {
 -        return cy.createResource(token, 'groups', {
 -            group: JSON.stringify(data),
 -            ensure_unique_name: true
 -        })
++    "doWebDAVRequest",
++    (method = "GET", path = "", data = null, qs = null, token = systemToken, auth = false, followRedirect = true, failOnStatusCode = true) => {
++        return cy.doRequest("GET", "/arvados/v1/config", null, null).then(({ body: config }) => {
++            return cy.request({
++                method: method,
++                url: `${config.Services.WebDAVDownload.ExternalURL.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`,
++                body: data,
++                qs: auth ? qs : Object.assign({ api_token: token }, qs),
++                auth: auth ? { bearer: `${token}` } : undefined,
++                followRedirect: followRedirect,
++                failOnStatusCode: failOnStatusCode,
++            });
++        });
+     }
 -)
++);
  
- Cypress.Commands.add("logsForContainer", (token, uuid, logType, logTextArray = []) => {
-     let logs = [];
-     for (const logText of logTextArray) {
-         logs.push(
-             cy
-                 .createLog(token, {
-                     object_uuid: uuid,
-                     event_type: logType,
-                     properties: {
-                         text: logText,
-                     },
 -Cypress.Commands.add(
 -    "trashGroup", (token, uuid) => {
 -        return cy.deleteResource(token, 'groups', uuid);
 -    }
 -)
++Cypress.Commands.add("getUser", (username, first_name = "", last_name = "", is_admin = false, is_active = true) => {
++    // Create user if not already created
++    return (
++        cy
++            .doRequest(
++                "POST",
++                "/auth/controller/callback",
++                {
++                    auth_info: JSON.stringify({
++                        email: `${username}@example.local`,
++                        username: username,
++                        first_name: first_name,
++                        last_name: last_name,
++                        alternate_emails: [],
++                    }),
++                    return_to: ",https://example.local",
++                },
++                null,
++                systemToken,
++                true,
++                false
++            ) // Don't follow redirects so we can catch the token
++            .its("headers.location")
++            .as("location")
++            // Get its token and set the account up as admin and/or active
++            .then(
++                function () {
++                    this.userToken = this.location.split("=")[1];
++                    assert.isString(this.userToken);
++                    return cy
++                        .doRequest("GET", "/arvados/v1/users", null, {
++                            filters: `[["username", "=", "${username}"]]`,
++                        })
++                        .its("body.items.0")
++                        .as("aUser")
++                        .then(function () {
++                            cy.doRequest("PUT", `/arvados/v1/users/${this.aUser.uuid}`, {
++                                user: {
++                                    is_admin: is_admin,
++                                    is_active: is_active,
++                                },
++                            }).as("lastLogRecord");
++                        });
++                },
++                cy.getAll("@lastLogRecord").then(function () {
++                    return logs;
 +                })
-                 .as("lastLogRecord")
-         );
-     }
-     cy.getAll("@lastLogRecord").then(function () {
-         return logs;
-     });
++            )
++    );
 +});
  
 +Cypress.Commands.add("createVirtualMachine", (token, data) => {
 +    return cy.createResource(token, "virtual_machines", {
 +        virtual_machine: JSON.stringify(data),
 +        ensure_unique_name: true,
 +    });
 +});
  
 -Cypress.Commands.add(
 -    "createWorkflow", (token, data) => {
 -        return cy.createResource(token, 'workflows', {
 -            workflow: JSON.stringify(data),
 -            ensure_unique_name: true
 -        })
 -    }
 -)
 +Cypress.Commands.add("getResource", (token, suffix, uuid) => {
 +    return cy
 +        .doRequest("GET", `/arvados/v1/${suffix}/${uuid}`, null, {}, token)
 +        .its("body")
 +        .then(function (resource) {
 +            return resource;
 +        });
 +});
  
 -Cypress.Commands.add(
 -    "createCollection", (token, data) => {
 -        return cy.createResource(token, 'collections', {
 -            collection: JSON.stringify(data),
 -            ensure_unique_name: true
 -        })
 -    }
 -)
 +Cypress.Commands.add("createResource", (token, suffix, data) => {
 +    return cy
 +        .doRequest("POST", "/arvados/v1/" + suffix, data, null, token, true)
 +        .its("body")
 +        .then(function (resource) {
 +            createdResources.push({ suffix, uuid: resource.uuid });
 +            return resource;
 +        });
 +});
  
 -Cypress.Commands.add(
 -    "getCollection", (token, uuid) => {
 -        return cy.getResource(token, 'collections', uuid)
 -    }
 -)
 +Cypress.Commands.add("deleteResource", (token, suffix, uuid, failOnStatusCode = true) => {
 +    return cy
 +        .doRequest("DELETE", "/arvados/v1/" + suffix + "/" + uuid, null, null, token, false, true, failOnStatusCode)
 +        .its("body")
 +        .then(function (resource) {
 +            return resource;
 +        });
 +});
  
 -Cypress.Commands.add(
 -    "updateCollection", (token, uuid, data) => {
 -        return cy.updateResource(token, 'collections', uuid, {
 -            collection: JSON.stringify(data)
 -        })
 -    }
 -)
 +Cypress.Commands.add("updateResource", (token, suffix, uuid, data) => {
 +    return cy
 +        .doRequest("PATCH", "/arvados/v1/" + suffix + "/" + uuid, data, null, token, true)
 +        .its("body")
 +        .then(function (resource) {
 +            return resource;
 +        });
 +});
  
- Cypress.Commands.add("loginAs", user => {
-     cy.clearCookies();
-     cy.clearLocalStorage();
-     cy.visit(`/token/?api_token=${user.token}`);
-     cy.url({ timeout: 10000 }).should("contain", "/projects/");
-     cy.get("div#root").should("contain", "Arvados Workbench (zzzzz)");
-     cy.get("div#root").should("not.contain", "Your account is inactive");
 -Cypress.Commands.add(
 -    "collectionReplaceFiles", (token, uuid, data) => {
 -        return cy.updateResource(token, 'collections', uuid, {
 -            collection: {
 -                preserve_version: true,
 -            },
 -            replace_files: JSON.stringify(data)
 -        })
 -    }
 -)
++Cypress.Commands.add("createCollection", (token, data) => {
++    return cy.createResource(token, "collections", {
++        collection: JSON.stringify(data),
++        ensure_unique_name: true,
++    });
++});
+ 
 -Cypress.Commands.add(
 -    "getContainer", (token, uuid) => {
 -        return cy.getResource(token, 'containers', uuid)
 -    }
 -)
++Cypress.Commands.add("getCollection", (token, uuid) => {
++    return cy.getResource(token, "collections", uuid);
++});
+ 
 -Cypress.Commands.add(
 -    "updateContainer", (token, uuid, data) => {
 -        return cy.updateResource(token, 'containers', uuid, {
 -            container: JSON.stringify(data)
 -        })
 -    }
 -)
++Cypress.Commands.add("updateCollection", (token, uuid, data) => {
++    return cy.updateResource(token, "collections", uuid, {
++        collection: JSON.stringify(data),
++    });
++});
+ 
 -Cypress.Commands.add(
 -    "getContainerRequest", (token, uuid) => {
 -        return cy.getResource(token, 'container_requests', uuid)
 -    }
 -)
++Cypress.Commands.add("collectionReplaceFiles", (token, uuid, data) => {
++    return cy.updateResource(token, "collections", uuid, {
++        collection: {
++            preserve_version: true,
++        },
++        replace_files: JSON.stringify(data),
++    });
 +});
  
- Cypress.Commands.add("testEditProjectOrCollection", (container, oldName, newName, newDescription, isProject = true) => {
-     cy.get(container).contains(oldName).rightclick();
-     cy.get("[data-cy=context-menu]")
-         .contains(isProject ? "Edit project" : "Edit collection")
-         .click();
-     cy.get("[data-cy=form-dialog]").within(() => {
-         cy.get("input[name=name]").clear().type(newName);
-         cy.get(isProject ? "div[contenteditable=true]" : "input[name=description]")
-             .clear()
-             .type(newDescription);
-         cy.get("[data-cy=form-submit-btn]").click();
 -Cypress.Commands.add(
 -    'createContainerRequest', (token, data) => {
 -        return cy.createResource(token, 'container_requests', {
 -            container_request: JSON.stringify(data),
 -            ensure_unique_name: true
 -        })
 -    }
 -)
++Cypress.Commands.add("getContainer", (token, uuid) => {
++    return cy.getResource(token, "containers", uuid);
++});
+ 
 -Cypress.Commands.add(
 -    "updateContainerRequest", (token, uuid, data) => {
 -        return cy.updateResource(token, 'container_requests', uuid, {
 -            container_request: JSON.stringify(data)
 -        })
 -    }
 -)
++Cypress.Commands.add("updateContainer", (token, uuid, data) => {
++    return cy.updateResource(token, "containers", uuid, {
++        container: JSON.stringify(data),
 +    });
++});
 +
-     cy.get(container).contains(newName).rightclick();
-     cy.get("[data-cy=context-menu]")
-         .contains(isProject ? "Edit project" : "Edit collection")
-         .click();
-     cy.get("[data-cy=form-dialog]").within(() => {
-         cy.get("input[name=name]").should("have.value", newName);
++Cypress.Commands.add("getContainerRequest", (token, uuid) => {
++    return cy.getResource(token, "container_requests", uuid);
++});
 +
-         if (isProject) {
-             cy.get("span[data-text=true]").contains(newDescription);
-         } else {
-             cy.get("input[name=description]").should("have.value", newDescription);
-         }
++Cypress.Commands.add("createContainerRequest", (token, data) => {
++    return cy.createResource(token, "container_requests", {
++        container_request: JSON.stringify(data),
++        ensure_unique_name: true,
++    });
++});
 +
-         cy.get("[data-cy=form-cancel-btn]").click();
++Cypress.Commands.add("updateContainerRequest", (token, uuid, data) => {
++    return cy.updateResource(token, "container_requests", uuid, {
++        container_request: JSON.stringify(data),
 +    });
 +});
  
+ /**
+  * Requires an admin token for log_uuid modification to succeed
+  */
 -Cypress.Commands.add(
 -    "appendLog", (token, crUuid, fileName, lines = []) => (
 -        cy.getContainerRequest(token, crUuid).then((containerRequest) => {
 -            if (containerRequest.log_uuid) {
 -                cy.listContainerRequestLogs(token, crUuid).then((logFiles) => {
 -                    const filePath = `${containerRequest.log_uuid}/${containerLogFolderPrefix}${containerRequest.container_uuid}/${fileName}`;
 -                    if (logFiles.find((file) => (file.name === fileName))) {
 -                        // File exists, fetch and append
 -                        return cy.doWebDAVRequest(
 -                                "GET",
 -                                `c=${filePath}`,
 -                                null,
 -                                null,
 -                                token
 -                            )
 -                            .then(({ body: contents }) => cy.doWebDAVRequest(
 -                                "PUT",
 -                                `c=${filePath}`,
 -                                contents.split("\n").concat(lines).join("\n"),
 -                                null,
 -                                token
 -                            ));
 -                    } else {
 -                        // File not exists, put new file
 -                        cy.doWebDAVRequest(
 -                            "PUT",
 -                            `c=${filePath}`,
 -                            lines.join("\n"),
 -                            null,
 -                            token
 -                        )
 -                    }
 -                });
 -            } else {
 -                // Create log collection
 -                return cy.createCollection(token, {
++Cypress.Commands.add("appendLog", (token, crUuid, fileName, lines = []) =>
++    cy.getContainerRequest(token, crUuid).then(containerRequest => {
++        if (containerRequest.log_uuid) {
++            cy.listContainerRequestLogs(token, crUuid).then(logFiles => {
++                const filePath = `${containerRequest.log_uuid}/${containerLogFolderPrefix}${containerRequest.container_uuid}/${fileName}`;
++                if (logFiles.find(file => file.name === fileName)) {
++                    // File exists, fetch and append
++                    return cy
++                        .doWebDAVRequest("GET", `c=${filePath}`, null, null, token)
++                        .then(({ body: contents }) =>
++                            cy.doWebDAVRequest("PUT", `c=${filePath}`, contents.split("\n").concat(lines).join("\n"), null, token)
++                        );
++                } else {
++                    // File not exists, put new file
++                    cy.doWebDAVRequest("PUT", `c=${filePath}`, lines.join("\n"), null, token);
++                }
++            });
++        } else {
++            // Create log collection
++            return cy
++                .createCollection(token, {
+                     name: `Test log collection ${Math.floor(Math.random() * 999999)}`,
+                     owner_uuid: containerRequest.owner_uuid,
 -                    manifest_text: ""
 -                }).then((collection) => {
++                    manifest_text: "",
++                })
++                .then(collection => {
+                     // Update CR log_uuid to fake log collection
+                     cy.updateContainerRequest(token, containerRequest.uuid, {
+                         log_uuid: collection.uuid,
 -                    }).then(() => (
++                    }).then(() =>
+                         // Create empty directory for container uuid
 -                        cy.collectionReplaceFiles(token, collection.uuid, {
 -                            [`/${containerLogFolderPrefix}${containerRequest.container_uuid}`]: "d41d8cd98f00b204e9800998ecf8427e+0"
 -                        }).then(() => (
 -                            // Put new log file with contents into fake log collection
 -                            cy.doWebDAVRequest(
 -                                'PUT',
 -                                `c=${collection.uuid}/${containerLogFolderPrefix}${containerRequest.container_uuid}/${fileName}`,
 -                                lines.join('\n'),
 -                                null,
 -                                token
++                        cy
++                            .collectionReplaceFiles(token, collection.uuid, {
++                                [`/${containerLogFolderPrefix}${containerRequest.container_uuid}`]: "d41d8cd98f00b204e9800998ecf8427e+0",
++                            })
++                            .then(() =>
++                                // Put new log file with contents into fake log collection
++                                cy.doWebDAVRequest(
++                                    "PUT",
++                                    `c=${collection.uuid}/${containerLogFolderPrefix}${containerRequest.container_uuid}/${fileName}`,
++                                    lines.join("\n"),
++                                    null,
++                                    token
++                                )
+                             )
 -                        ))
 -                    ));
++                    );
+                 });
 -            }
 -        })
 -    )
 -)
 -
 -Cypress.Commands.add(
 -    "listContainerRequestLogs", (token, crUuid) => (
 -        cy.getContainerRequest(token, crUuid).then((containerRequest) => (
 -            cy.doWebDAVRequest('PROPFIND', `c=${containerRequest.log_uuid}/${containerLogFolderPrefix}${containerRequest.container_uuid}`, null, null, token)
 -                .then(({body: data}) => {
 -                    return extractFilesData(new DOMParser().parseFromString(data, "text/xml"));
 -                })
 -        ))
 -    )
++        }
++    })
+ );
+ 
 -Cypress.Commands.add(
 -    "createVirtualMachine", (token, data) => {
 -        return cy.createResource(token, 'virtual_machines', {
 -            virtual_machine: JSON.stringify(data),
 -            ensure_unique_name: true
 -        })
 -    }
 -)
 -
 -Cypress.Commands.add(
 -    "getResource", (token, suffix, uuid) => {
 -        return cy.doRequest('GET', `/arvados/v1/${suffix}/${uuid}`, null, {}, token)
 -            .its('body')
 -            .then(function (resource) {
 -                return resource;
 -            })
 -    }
 -)
 -
 -Cypress.Commands.add(
 -    "createResource", (token, suffix, data) => {
 -        return cy.doRequest('POST', '/arvados/v1/' + suffix, data, null, token, true)
 -            .its('body')
 -            .then(function (resource) {
 -                createdResources.push({suffix, uuid: resource.uuid});
 -                return resource;
++Cypress.Commands.add("listContainerRequestLogs", (token, crUuid) =>
++    cy.getContainerRequest(token, crUuid).then(containerRequest =>
++        cy
++            .doWebDAVRequest(
++                "PROPFIND",
++                `c=${containerRequest.log_uuid}/${containerLogFolderPrefix}${containerRequest.container_uuid}`,
++                null,
++                null,
++                token
++            )
++            .then(({ body: data }) => {
++                return extractFilesData(new DOMParser().parseFromString(data, "text/xml"));
+             })
 -    }
 -)
 -
 -Cypress.Commands.add(
 -    "deleteResource", (token, suffix, uuid, failOnStatusCode = true) => {
 -        return cy.doRequest('DELETE', '/arvados/v1/' + suffix + '/' + uuid, null, null, token, false, true, failOnStatusCode)
 -            .its('body')
 -            .then(function (resource) {
 -                return resource;
 -            })
 -    }
 -)
 -
 -Cypress.Commands.add(
 -    "updateResource", (token, suffix, uuid, data) => {
 -        return cy.doRequest('PATCH', '/arvados/v1/' + suffix + '/' + uuid, data, null, token, true)
 -            .its('body')
 -            .then(function (resource) {
 -                return resource;
 -            })
 -    }
 -)
 -
 -Cypress.Commands.add(
 -    "loginAs", (user) => {
 -        cy.clearCookies()
 -        cy.clearLocalStorage()
 -        cy.visit(`/token/?api_token=${user.token}`);
 -        cy.url({timeout: 10000}).should('contain', '/projects/');
 -        cy.get('div#root').should('contain', 'Arvados Workbench (zzzzz)');
 -        cy.get('div#root').should('not.contain', 'Your account is inactive');
 -    }
 -)
 -
 -Cypress.Commands.add(
 -    "testEditProjectOrCollection", (container, oldName, newName, newDescription, isProject = true) => {
 -        cy.get(container).contains(oldName).rightclick();
 -        cy.get('[data-cy=context-menu]').contains(isProject ? 'Edit project' : 'Edit collection').click();
 -        cy.get('[data-cy=form-dialog]').within(() => {
 -            cy.get('input[name=name]').clear().type(newName);
 -            cy.get(isProject ? 'div[contenteditable=true]' : 'input[name=description]').clear().type(newDescription);
 -            cy.get('[data-cy=form-submit-btn]').click();
 -        });
 -
 -        cy.get(container).contains(newName).rightclick();
 -        cy.get('[data-cy=context-menu]').contains(isProject ? 'Edit project' : 'Edit collection').click();
 -        cy.get('[data-cy=form-dialog]').within(() => {
 -            cy.get('input[name=name]').should('have.value', newName);
 -
 -            if (isProject) {
 -                cy.get('span[data-text=true]').contains(newDescription);
 -            } else {
 -                cy.get('input[name=description]').should('have.value', newDescription);
 -            }
++    )
++);
+ 
 -            cy.get('[data-cy=form-cancel-btn]').click();
 -        });
 -    }
 -)
++cy.get("[data-cy=form-cancel-btn]").click();
+ 
 -Cypress.Commands.add(
 -    "doSearch", (searchTerm) => {
 -        cy.get('[data-cy=searchbar-input-field]').type(`{selectall}${searchTerm}{enter}`);
 -    }
 -)
 +Cypress.Commands.add("doSearch", searchTerm => {
 +    cy.get("[data-cy=searchbar-input-field]").type(`{selectall}${searchTerm}{enter}`);
 +    cy.get("[data-cy=searchbar-parent-form]").submit();
 +});
  
 -Cypress.Commands.add(
 -    "goToPath", (path) => {
 -        return cy.window().its('appHistory').invoke('push', path);
 -    }
 -)
 +Cypress.Commands.add("goToPath", path => {
 +    return cy.window().its("appHistory").invoke("push", path);
 +});
  
 -Cypress.Commands.add('getAll', (...elements) => {
 -    const promise = cy.wrap([], { log: false })
 +Cypress.Commands.add("getAll", (...elements) => {
 +    const promise = cy.wrap([], { log: false });
  
      for (let element of elements) {
 -        promise.then(arr => cy.get(element).then(got => cy.wrap([...arr, got])))
 +        promise.then(arr => cy.get(element).then(got => cy.wrap([...arr, got])));
      }
  
 -    return promise
 -})
 +    return promise;
 +});
  
 -Cypress.Commands.add('shareWith', (srcUserToken, targetUserUUID, itemUUID, permission = 'can_write') => {
 +Cypress.Commands.add("shareWith", (srcUserToken, targetUserUUID, itemUUID, permission = "can_write") => {
      cy.createLink(srcUserToken, {
          name: permission,
 -        link_class: 'permission',
 +        link_class: "permission",
          head_uuid: itemUUID,
 -        tail_uuid: targetUserUUID
 +        tail_uuid: targetUserUUID,
      });
 -})
 +});
  
 -Cypress.Commands.add('addToFavorites', (userToken, userUUID, itemUUID) => {
 +Cypress.Commands.add("addToFavorites", (userToken, userUUID, itemUUID) => {
      cy.createLink(userToken, {
          head_uuid: itemUUID,
 -        link_class: 'star',
 -        name: '',
 +        link_class: "star",
 +        name: "",
          owner_uuid: userUUID,
          tail_uuid: userUUID,
      });
diff --cc src/components/collection-panel-files/collection-panel-files.tsx
index 0fe933f5,c9d5b657..83de48de
--- a/src/components/collection-panel-files/collection-panel-files.tsx
+++ b/src/components/collection-panel-files/collection-panel-files.tsx
@@@ -2,14 -2,14 +2,14 @@@
  //
  // SPDX-License-Identifier: AGPL-3.0
  
 -import React from 'react';
 -import classNames from 'classnames';
 -import { connect } from 'react-redux';
 +import React from "react";
 +import classNames from "classnames";
 +import { connect } from "react-redux";
  import { FixedSizeList } from "react-window";
  import AutoSizer from "react-virtualized-auto-sizer";
 -import servicesProvider from 'common/service-provider';
 -import { DownloadIcon, MoreHorizontalIcon, MoreVerticalIcon } from 'components/icon/icon';
 -import { SearchInput } from 'components/search-input/search-input';
 +import servicesProvider from "common/service-provider";
- import { CustomizeTableIcon, DownloadIcon, MoreOptionsIcon } from "components/icon/icon";
++import { DownloadIcon, MoreHorizontalIcon, MoreVerticalIcon } from "components/icon/icon";
 +import { SearchInput } from "components/search-input/search-input";
  import {
      ListItemIcon,
      StyleRulesCallback,
@@@ -21,18 -21,25 +21,19 @@@
      Checkbox,
      CircularProgress,
      Button,
 -} from '@material-ui/core';
 -import { FileTreeData } from '../file-tree/file-tree-data';
 -import { TreeItem, TreeItemStatus } from '../tree/tree';
 -import { RootState } from 'store/store';
 -import { WebDAV, WebDAVRequestConfig } from 'common/webdav';
 -import { AuthState } from 'store/auth/auth-reducer';
 -import { extractFilesData } from 'services/collection-service/collection-service-files-response';
 -import {
 -    DefaultIcon,
 -    DirectoryIcon,
 -    FileIcon,
 -    BackIcon,
 -    SidePanelRightArrowIcon
 -} from 'components/icon/icon';
 -import { setCollectionFiles } from 'store/collection-panel/collection-panel-files/collection-panel-files-actions';
 -import { sortBy } from 'lodash';
 -import { formatFileSize } from 'common/formatters';
 -import { getInlineFileUrl, sanitizeToken } from 'views-components/context-menu/actions/helpers';
 -import { extractUuidKind, ResourceKind } from 'models/resource';
 +} from "@material-ui/core";
 +import { FileTreeData } from "../file-tree/file-tree-data";
 +import { TreeItem, TreeItemStatus } from "../tree/tree";
 +import { RootState } from "store/store";
 +import { WebDAV, WebDAVRequestConfig } from "common/webdav";
 +import { AuthState } from "store/auth/auth-reducer";
 +import { extractFilesData } from "services/collection-service/collection-service-files-response";
 +import { DefaultIcon, DirectoryIcon, FileIcon, BackIcon, SidePanelRightArrowIcon } from "components/icon/icon";
 +import { setCollectionFiles } from "store/collection-panel/collection-panel-files/collection-panel-files-actions";
 +import { sortBy } from "lodash";
 +import { formatFileSize } from "common/formatters";
 +import { getInlineFileUrl, sanitizeToken } from "views-components/context-menu/actions/helpers";
++import { extractUuidKind, ResourceKind } from "models/resource";
  
  export interface CollectionPanelFilesProps {
      isWritable: boolean;
@@@ -220,470 -226,373 +221,486 @@@ const styles: StyleRulesCallback<CssRul
  
  const pathPromise = {};
  
 -export const CollectionPanelFiles = withStyles(styles)(connect((state: RootState) => ({
 -    auth: state.auth,
 -    collectionPanel: state.collectionPanel,
 -    collectionPanelFiles: state.collectionPanelFiles,
 -}))((props: CollectionPanelFilesProps & WithStyles<CssRules> & { auth: AuthState }) => {
 -    const { classes, onItemMenuOpen, onUploadDataClick, isWritable, dispatch, collectionPanelFiles, collectionPanel } = props;
 -    const { apiToken, config } = props.auth;
 -
 -    const webdavClient = new WebDAV({
 -        baseURL: config.keepWebServiceUrl,
 -        headers: {
 -            Authorization: `Bearer ${apiToken}`
 -        },
 -    });
 +export const CollectionPanelFiles = withStyles(styles)(
 +    connect((state: RootState) => ({
 +        auth: state.auth,
 +        collectionPanel: state.collectionPanel,
 +        collectionPanelFiles: state.collectionPanelFiles,
 +    }))((props: CollectionPanelFilesProps & WithStyles<CssRules> & { auth: AuthState }) => {
 +        const { classes, onItemMenuOpen, onUploadDataClick, isWritable, dispatch, collectionPanelFiles, collectionPanel } = props;
 +        const { apiToken, config } = props.auth;
 +
 +        const webdavClient = new WebDAV({
 +            baseURL: config.keepWebServiceUrl,
 +            headers: {
 +                Authorization: `Bearer ${apiToken}`,
 +            },
 +        });
 +
 +        const webDAVRequestConfig: WebDAVRequestConfig = {
 +            headers: {
 +                Depth: "1",
 +            },
 +        };
  
 -    const webDAVRequestConfig: WebDAVRequestConfig = {
 -        headers: {
 -            Depth: '1',
 -        },
 -    };
 -
 -    const parentRef = React.useRef(null);
 -    const [path, setPath] = React.useState<string[]>([]);
 -    const [pathData, setPathData] = React.useState({});
 -    const [isLoading, setIsLoading] = React.useState(false);
 -    const [leftSearch, setLeftSearch] = React.useState('');
 -    const [rightSearch, setRightSearch] = React.useState('');
 -
 -    const leftKey = (path.length > 1 ? path.slice(0, path.length - 1) : path).join('/');
 -    const rightKey = path.join('/');
 -
 -    const leftData = pathData[leftKey] || [];
 -    const rightData = pathData[rightKey];
 -
 -    React.useEffect(() => {
 -        if (props.currentItemUuid && extractUuidKind(props.currentItemUuid) === ResourceKind.COLLECTION) {
 -            setPathData({});
 -            setPath([props.currentItemUuid]);
 -        }
 -    }, [props.currentItemUuid]);
 -
 -    const fetchData = (keys, ignoreCache = false) => {
 -        const keyArray = Array.isArray(keys) ? keys : [keys];
 -
 -        Promise.all(keyArray.filter(key => !!key)
 -            .map((key) => {
 -                const dataExists = !!pathData[key];
 -                const runningRequest = pathPromise[key];
 -
 -                if (ignoreCache || (!dataExists && !runningRequest)) {
 -                    if (!isLoading) {
 -                        setIsLoading(true);
 -                    }
 +        const parentRef = React.useRef(null);
 +        const [path, setPath] = React.useState<string[]>([]);
 +        const [pathData, setPathData] = React.useState({});
 +        const [isLoading, setIsLoading] = React.useState(false);
 +        const [leftSearch, setLeftSearch] = React.useState("");
 +        const [rightSearch, setRightSearch] = React.useState("");
  
 -                    pathPromise[key] = true;
 +        const leftKey = (path.length > 1 ? path.slice(0, path.length - 1) : path).join("/");
 +        const rightKey = path.join("/");
  
 -                    return webdavClient.propfind(`c=${key}`, webDAVRequestConfig);
 -                }
 +        const leftData = pathData[leftKey] || [];
 +        const rightData = pathData[rightKey];
  
 -                return Promise.resolve(null);
 -            })
 -            .filter((promise) => !!promise)
 -        )
 -            .then((requests) => {
 -                const newState = requests.map((request, index) => {
 -                    if (request && request.responseXML != null) {
 -                        const key = keyArray[index];
 -                        const result: any = extractFilesData(request.responseXML);
 -                        const sortedResult = sortBy(result, (n) => n.name).sort((n1, n2) => {
 -                            if (n1.type === 'directory' && n2.type !== 'directory') {
 -                                return -1;
 +        React.useEffect(() => {
-             if (props.currentItemUuid) {
++            if (props.currentItemUuid && extractUuidKind(props.currentItemUuid) === ResourceKind.COLLECTION) {
 +                setPathData({});
 +                setPath([props.currentItemUuid]);
 +            }
 +        }, [props.currentItemUuid]);
 +
 +        const fetchData = (keys, ignoreCache = false) => {
 +            const keyArray = Array.isArray(keys) ? keys : [keys];
 +
 +            Promise.all(
 +                keyArray
 +                    .filter(key => !!key)
 +                    .map(key => {
 +                        const dataExists = !!pathData[key];
 +                        const runningRequest = pathPromise[key];
 +
 +                        if (ignoreCache || (!dataExists && !runningRequest)) {
 +                            if (!isLoading) {
 +                                setIsLoading(true);
                              }
 -                            if (n1.type !== 'directory' && n2.type === 'directory') {
 -                                return 1;
 +
 +                            pathPromise[key] = true;
 +
 +                            return webdavClient.propfind(`c=${key}`, webDAVRequestConfig);
 +                        }
 +
 +                        return Promise.resolve(null);
 +                    })
 +                    .filter(promise => !!promise)
 +            )
 +                .then(requests => {
 +                    const newState = requests
 +                        .map((request, index) => {
 +                            if (request && request.responseXML != null) {
 +                                const key = keyArray[index];
 +                                const result: any = extractFilesData(request.responseXML);
 +                                const sortedResult = sortBy(result, n => n.name).sort((n1, n2) => {
 +                                    if (n1.type === "directory" && n2.type !== "directory") {
 +                                        return -1;
 +                                    }
 +                                    if (n1.type !== "directory" && n2.type === "directory") {
 +                                        return 1;
 +                                    }
 +                                    return 0;
 +                                });
 +
 +                                return { [key]: sortedResult };
                              }
 -                            return 0;
 -                        });
 +                            return {};
 +                        })
 +                        .reduce((prev, next) => {
 +                            return { ...next, ...prev };
 +                        }, {});
 +                    setPathData(state => ({ ...state, ...newState }));
 +                })
 +                .finally(() => {
 +                    setIsLoading(false);
 +                    keyArray.forEach(key => delete pathPromise[key]);
 +                });
 +        };
  
 -                        return { [key]: sortedResult };
 -                    }
 -                    return {};
 -                }).reduce((prev, next) => {
 -                    return { ...next, ...prev };
 -                }, {});
 -                setPathData((state) => ({ ...state, ...newState }));
 -            })
 -            .finally(() => {
 -                setIsLoading(false);
 -                keyArray.forEach(key => delete pathPromise[key]);
 -            });
 -    };
 -
 -    React.useEffect(() => {
 -        if (rightKey) {
 -            fetchData(rightKey);
 -            setLeftSearch('');
 -            setRightSearch('');
 -        }
 -    }, [rightKey, rightData]); // eslint-disable-line react-hooks/exhaustive-deps
 -
 -    const currentPDH = (collectionPanel.item || {}).portableDataHash;
 -    React.useEffect(() => {
 -        if (currentPDH) {
 -            fetchData([leftKey, rightKey], true);
 -        }
 -    }, [currentPDH]); // eslint-disable-line react-hooks/exhaustive-deps
 -
 -    React.useEffect(() => {
 -        if (rightData) {
 -            const filtered = rightData.filter(({ name }) => name.indexOf(rightSearch) > -1);
 -            setCollectionFiles(filtered, false)(dispatch);
 -        }
 -    }, [rightData, dispatch, rightSearch]);
 -
 -    const handleRightClick = React.useCallback(
 -        (event) => {
 -            event.preventDefault();
 -            let elem = event.target;
 -
 -            while (elem && elem.dataset && !elem.dataset.item) {
 -                elem = elem.parentNode;
 +        React.useEffect(() => {
 +            if (rightKey) {
 +                fetchData(rightKey);
 +                setLeftSearch("");
 +                setRightSearch("");
              }
-         }, [rightKey]); // eslint-disable-line react-hooks/exhaustive-deps
++        }, [rightKey, rightData]); // eslint-disable-line react-hooks/exhaustive-deps
  
 -            if (!elem || !elem.dataset) {
 -                return;
 +        const currentPDH = (collectionPanel.item || {}).portableDataHash;
 +        React.useEffect(() => {
 +            if (currentPDH) {
 +                fetchData([leftKey, rightKey], true);
              }
 +        }, [currentPDH]); // eslint-disable-line react-hooks/exhaustive-deps
  
 -            const { id } = elem.dataset;
 +        React.useEffect(() => {
 +            if (rightData) {
 +                const filtered = rightData.filter(({ name }) => name.indexOf(rightSearch) > -1);
 +                setCollectionFiles(filtered, false)(dispatch);
 +            }
 +        }, [rightData, dispatch, rightSearch]);
  
 -            const item: any = {
 -                id,
 -                data: rightData.find((elem) => elem.id === id),
 -            };
 +        const handleRightClick = React.useCallback(
 +            event => {
 +                event.preventDefault();
 +                let elem = event.target;
  
 -            if (id) {
 -                onItemMenuOpen(event, item, isWritable);
 -            }
 -        },
 -        [onItemMenuOpen, isWritable, rightData]);
 +                while (elem && elem.dataset && !elem.dataset.item) {
 +                    elem = elem.parentNode;
 +                }
  
 -    React.useEffect(() => {
 -        let node = null;
 +                if (!elem || !elem.dataset) {
 +                    return;
 +                }
  
 -        if (parentRef?.current) {
 -            node = parentRef.current;
 -            (node as any).addEventListener('contextmenu', handleRightClick);
 -        }
 +                const { id } = elem.dataset;
  
 -        return () => {
 -            if (node) {
 -                (node as any).removeEventListener('contextmenu', handleRightClick);
 -            }
 -        };
 -    }, [parentRef, handleRightClick]);
 +                const item: any = {
 +                    id,
 +                    data: rightData.find(elem => elem.id === id),
 +                };
  
 -    const handleClick = React.useCallback(
 -        (event: any) => {
 -            let isCheckbox = false;
 -            let isMoreButton = false;
 -            let elem = event.target;
 +                if (id) {
 +                    onItemMenuOpen(event, item, isWritable);
 +                }
 +            },
 +            [onItemMenuOpen, isWritable, rightData]
 +        );
  
 -            if (elem.type === 'checkbox') {
 -                isCheckbox = true;
 -            }
 -            // The "More options" button click event could be triggered on its
 -            // internal graphic element.
 -            else if ((elem.dataset && elem.dataset.id === 'moreOptions') || (elem.parentNode && elem.parentNode.dataset && elem.parentNode.dataset.id === 'moreOptions')) {
 -                isMoreButton = true;
 -            }
 +        React.useEffect(() => {
 +            let node = null;
  
 -            while (elem && elem.dataset && !elem.dataset.item) {
 -                elem = elem.parentNode;
 +            if (parentRef?.current) {
 +                node = parentRef.current;
 +                (node as any).addEventListener("contextmenu", handleRightClick);
              }
  
 -            if (elem && elem.dataset && !isCheckbox && !isMoreButton) {
 -                const { parentPath, subfolderPath, breadcrumbPath, type } = elem.dataset;
 -
 -                if (breadcrumbPath) {
 -                    const index = path.indexOf(breadcrumbPath);
 -                    setPath((state) => ([...state.slice(0, index + 1)]));
 +            return () => {
 +                if (node) {
 +                    (node as any).removeEventListener("contextmenu", handleRightClick);
                  }
 +            };
 +        }, [parentRef, handleRightClick]);
  
 -                if (parentPath && type === 'directory') {
 -                    if (path.length > 1) {
 -                        path.pop()
 -                    }
 +        const handleClick = React.useCallback(
 +            (event: any) => {
 +                let isCheckbox = false;
 +                let isMoreButton = false;
 +                let elem = event.target;
  
 -                    setPath((state) => ([...state, parentPath]));
 +                if (elem.type === "checkbox") {
 +                    isCheckbox = true;
                  }
 -
 -                if (subfolderPath && type === 'directory') {
 -                    setPath((state) => ([...state, subfolderPath]));
 +                // The "More options" button click event could be triggered on its
 +                // internal graphic element.
 +                else if (
 +                    (elem.dataset && elem.dataset.id === "moreOptions") ||
 +                    (elem.parentNode && elem.parentNode.dataset && elem.parentNode.dataset.id === "moreOptions")
 +                ) {
 +                    isMoreButton = true;
                  }
  
 -                if (elem.dataset.id && type === 'file') {
 -                    const item = rightData.find(({ id }) => id === elem.dataset.id) || leftData.find(({ id }) => id === elem.dataset.id);
 -                    const enhancedItem = servicesProvider.getServices().collectionService.extendFileURL(item);
 -                    const fileUrl = sanitizeToken(getInlineFileUrl(enhancedItem.url, config.keepWebServiceUrl, config.keepWebInlineServiceUrl), true);
 -                    window.open(fileUrl, '_blank');
 +                while (elem && elem.dataset && !elem.dataset.item) {
 +                    elem = elem.parentNode;
                  }
 -            }
  
 -            if (isCheckbox) {
 -                const { id } = elem.dataset;
 -                const item = collectionPanelFiles[id];
 -                props.onSelectionToggle(event, item);
 -            }
 -            if (isMoreButton) {
 -                const { id } = elem.dataset;
 -                const item: any = {
 -                    id,
 -                    data: rightData.find((elem) => elem.id === id),
 -                };
 -                onItemMenuOpen(event, item, isWritable);
 -            }
 -        },
 -        [path, setPath, collectionPanelFiles] // eslint-disable-line react-hooks/exhaustive-deps
 -    );
 -
 -    const getItemIcon = React.useCallback(
 -        (type: string, activeClass: string | null) => {
 -            let Icon = DefaultIcon;
 -
 -            switch (type) {
 -                case 'directory':
 -                    Icon = DirectoryIcon;
 -                    break;
 -                case 'file':
 -                    Icon = FileIcon;
 -                    break;
 -            }
 +                if (elem && elem.dataset && !isCheckbox && !isMoreButton) {
 +                    const { parentPath, subfolderPath, breadcrumbPath, type } = elem.dataset;
  
 -            return (
 -                <ListItemIcon className={classNames(classes.listItemIcon, activeClass)}>
 -                    <Icon />
 -                </ListItemIcon>
 -            )
 -        },
 -        [classes]
 -    );
 +                    if (breadcrumbPath) {
 +                        const index = path.indexOf(breadcrumbPath);
 +                        setPath(state => [...state.slice(0, index + 1)]);
 +                    }
  
 -    const getActiveClass = React.useCallback(
 -        (name) => {
 -            return path[path.length - 1] === name ? classes.rowActive : null;
 -        },
 -        [path, classes]
 -    );
 +                    if (parentPath && type === "directory") {
 +                        if (path.length > 1) {
 +                            path.pop();
 +                        }
  
 -    const onOptionsMenuOpen = React.useCallback(
 -        (ev, isWritable) => {
 -            props.onOptionsMenuOpen(ev, isWritable);
 -        },
 -        [props.onOptionsMenuOpen] // eslint-disable-line react-hooks/exhaustive-deps
 -    );
 -
 -    return <div data-cy="collection-files-panel" onClick={handleClick} ref={parentRef}>
 -        <div className={classes.pathPanel}>
 -            <div className={classes.pathPanelPathWrapper}>
 -                {path.map((p: string, index: number) =>
 -                    <span key={`${index}-${p}`} data-item="true"
 -                        className={classes.pathPanelItem} data-breadcrumb-path={p}>
 -                        <span className={classes.rowActive}>{index === 0 ? 'Home' : p}</span> <b>/</b> 
 -                    </span>)
 +                        setPath(state => [...state, parentPath]);
 +                    }
 +
 +                    if (subfolderPath && type === "directory") {
 +                        setPath(state => [...state, subfolderPath]);
 +                    }
 +
 +                    if (elem.dataset.id && type === "file") {
 +                        const item = rightData.find(({ id }) => id === elem.dataset.id) || leftData.find(({ id }) => id === elem.dataset.id);
 +                        const enhancedItem = servicesProvider.getServices().collectionService.extendFileURL(item);
 +                        const fileUrl = sanitizeToken(
 +                            getInlineFileUrl(enhancedItem.url, config.keepWebServiceUrl, config.keepWebInlineServiceUrl),
 +                            true
 +                        );
 +                        window.open(fileUrl, "_blank");
 +                    }
                  }
 -            </div>
 -            <Tooltip className={classes.pathPanelMenu} title="More options" disableFocusListener>
 -                <IconButton data-cy='collection-files-panel-options-btn'
 -                    onClick={(ev) => {
 -                        onOptionsMenuOpen(ev, isWritable);
 -                    }}>
 -                    <MoreVerticalIcon />
 -                </IconButton>
 -            </Tooltip>
 -        </div>
 -        <div className={classes.wrapper}>
 -            <div className={classNames(classes.leftPanel, path.length > 1 ? classes.leftPanelVisible : classes.leftPanelHidden)} data-cy="collection-files-left-panel">
 -                <Tooltip title="Go back" className={path.length > 1 ? classes.backButton : classes.backButtonHidden}>
 -                    <IconButton onClick={() => setPath((state) => ([...state.slice(0, state.length - 1)]))}>
 -                        <BackIcon />
 -                    </IconButton>
 -                </Tooltip>
 -                <div className={path.length > 1 ? classes.searchWrapper : classes.searchWrapperHidden}>
 -                    <SearchInput selfClearProp={leftKey} label="Search" value={leftSearch} onSearch={setLeftSearch} />
 -                </div>
 -                <div className={classes.dataWrapper}>{leftData
 -                    ? <AutoSizer defaultWidth={0}>{({ height, width }) => {
 -                        const filtered = leftData.filter(({ name }) => name.indexOf(leftSearch) > -1);
 -                        return !!filtered.length
 -                            ? <FixedSizeList height={height} itemCount={filtered.length}
 -                                itemSize={35} width={width}>{({ index, style }) => {
 -                                    const { id, type, name } = filtered[index];
 -                                    return <div data-id={id} style={style} data-item="true"
 -                                        data-type={type} data-parent-path={name}
 -                                        className={classNames(classes.row, getActiveClass(name))}
 -                                        key={id}>
 -                                        {getItemIcon(type, getActiveClass(name))}
 -                                        <div className={classes.rowName}>
 -                                            {name}
 -                                        </div>
 -                                        {getActiveClass(name)
 -                                            ? <SidePanelRightArrowIcon
 -                                                style={{ display: 'inline', marginTop: '5px', marginLeft: '5px' }} />
 -                                            : null
 -                                        }
 -                                    </div>;
 -                                }}</FixedSizeList>
 -                            : <div className={classes.rowEmpty}>No directories available</div>
 -                    }}
 -                    </AutoSizer>
 -                    : <div data-cy="collection-loader" className={classes.row}><CircularProgress className={classes.loader} size={30} /></div>}
 -                </div>
 -            </div>
 -            <div className={classes.rightPanel} data-cy="collection-files-right-panel">
 -                <div className={classes.searchWrapper}>
 -                    <SearchInput selfClearProp={rightKey} label="Search" value={rightSearch} onSearch={setRightSearch} />
 +
 +                if (isCheckbox) {
 +                    const { id } = elem.dataset;
 +                    const item = collectionPanelFiles[id];
 +                    props.onSelectionToggle(event, item);
 +                }
 +                if (isMoreButton) {
 +                    const { id } = elem.dataset;
 +                    const item: any = {
 +                        id,
 +                        data: rightData.find(elem => elem.id === id),
 +                    };
 +                    onItemMenuOpen(event, item, isWritable);
 +                }
 +            },
 +            [path, setPath, collectionPanelFiles] // eslint-disable-line react-hooks/exhaustive-deps
 +        );
 +
 +        const getItemIcon = React.useCallback(
 +            (type: string, activeClass: string | null) => {
 +                let Icon = DefaultIcon;
 +
 +                switch (type) {
 +                    case "directory":
 +                        Icon = DirectoryIcon;
 +                        break;
 +                    case "file":
 +                        Icon = FileIcon;
 +                        break;
 +                }
 +
 +                return (
 +                    <ListItemIcon className={classNames(classes.listItemIcon, activeClass)}>
 +                        <Icon />
 +                    </ListItemIcon>
 +                );
 +            },
 +            [classes]
 +        );
 +
 +        const getActiveClass = React.useCallback(
 +            name => {
 +                return path[path.length - 1] === name ? classes.rowActive : null;
 +            },
 +            [path, classes]
 +        );
 +
 +        const onOptionsMenuOpen = React.useCallback(
 +            (ev, isWritable) => {
 +                props.onOptionsMenuOpen(ev, isWritable);
 +            },
 +            [props.onOptionsMenuOpen] // eslint-disable-line react-hooks/exhaustive-deps
 +        );
 +
 +        return (
 +            <div
 +                data-cy="collection-files-panel"
 +                onClick={handleClick}
-                 ref={parentRef}>
++                ref={parentRef}
++            >
 +                <div className={classes.pathPanel}>
 +                    <div className={classes.pathPanelPathWrapper}>
 +                        {path.map((p: string, index: number) => (
 +                            <span
 +                                key={`${index}-${p}`}
 +                                data-item="true"
 +                                className={classes.pathPanelItem}
-                                 data-breadcrumb-path={p}>
++                                data-breadcrumb-path={p}
++                            >
 +                                <span className={classes.rowActive}>{index === 0 ? "Home" : p}</span> <b>/</b> 
 +                            </span>
 +                        ))}
 +                    </div>
 +                    <Tooltip
 +                        className={classes.pathPanelMenu}
 +                        title="More options"
-                         disableFocusListener>
++                        disableFocusListener
++                    >
 +                        <IconButton
 +                            data-cy="collection-files-panel-options-btn"
 +                            onClick={ev => {
 +                                onOptionsMenuOpen(ev, isWritable);
-                             }}>
-                             <CustomizeTableIcon />
++                            }}
++                        >
++                            <MoreVerticalIcon />
 +                        </IconButton>
 +                    </Tooltip>
                  </div>
 -                {isWritable &&
 -                    <Button className={classes.uploadButton} data-cy='upload-button'
 -                        onClick={() => {
 -                            onUploadDataClick(rightKey === leftKey ? undefined : rightKey);
 -                        }}
 -                        variant='contained' color='primary' size='small'>
 -                        <DownloadIcon className={classes.uploadIcon} />
 -                        Upload data
 -                    </Button>}
 -                <div className={classes.dataWrapper}>{rightData && !isLoading
 -                    ? <AutoSizer defaultHeight={500}>{({ height, width }) => {
 -                        const filtered = rightData.filter(({ name }) => name.indexOf(rightSearch) > -1);
 -                        return !!filtered.length
 -                            ? <FixedSizeList height={height} itemCount={filtered.length}
 -                                itemSize={35} width={width}>{({ index, style }) => {
 -                                    const { id, type, name, size } = filtered[index];
 -
 -                                    return <div style={style} data-id={id} data-item="true"
 -                                        data-type={type} data-subfolder-path={name}
 -                                        className={classes.row} key={id}>
 -                                        <Checkbox color="primary"
 -                                            className={classes.rowSelection}
 -                                            checked={collectionPanelFiles[id] ? collectionPanelFiles[id].value.selected : false}
 -                                        /> 
 -                                        {getItemIcon(type, null)}
 -                                        <div className={classes.rowName}>
 -                                            {name}
 -                                        </div>
 -                                        <span className={classes.rowName} style={{
 -                                            marginLeft: 'auto', marginRight: '1rem'
 -                                        }}>
 -                                            {formatFileSize(size)}
 -                                        </span>
 -                                        <Tooltip title="More options" disableFocusListener>
 -                                            <IconButton data-id='moreOptions'
 -                                                data-cy='file-item-options-btn'
 -                                                className={classes.moreOptionsButton}>
 -                                                <MoreHorizontalIcon
 -                                                    data-id='moreOptions'
 -                                                    className={classes.moreOptions} />
 -                                            </IconButton>
 -                                        </Tooltip>
 -                                    </div>
 -                                }}</FixedSizeList>
 -                            : <div className={classes.rowEmpty}>This collection is empty</div>
 -                    }}</AutoSizer>
 -                    : <div className={classes.row}>
 -                        <CircularProgress className={classes.loader} size={30} />
 -                    </div>}
 +                <div className={classes.wrapper}>
 +                    <div
 +                        className={classNames(classes.leftPanel, path.length > 1 ? classes.leftPanelVisible : classes.leftPanelHidden)}
-                         data-cy="collection-files-left-panel">
++                        data-cy="collection-files-left-panel"
++                    >
 +                        <Tooltip
 +                            title="Go back"
-                             className={path.length > 1 ? classes.backButton : classes.backButtonHidden}>
++                            className={path.length > 1 ? classes.backButton : classes.backButtonHidden}
++                        >
 +                            <IconButton onClick={() => setPath(state => [...state.slice(0, state.length - 1)])}>
 +                                <BackIcon />
 +                            </IconButton>
 +                        </Tooltip>
 +                        <div className={path.length > 1 ? classes.searchWrapper : classes.searchWrapperHidden}>
 +                            <SearchInput
 +                                selfClearProp={leftKey}
 +                                label="Search"
 +                                value={leftSearch}
 +                                onSearch={setLeftSearch}
 +                            />
 +                        </div>
 +                        <div className={classes.dataWrapper}>
 +                            {leftData ? (
 +                                <AutoSizer defaultWidth={0}>
 +                                    {({ height, width }) => {
 +                                        const filtered = leftData.filter(({ name }) => name.indexOf(leftSearch) > -1);
 +                                        return !!filtered.length ? (
 +                                            <FixedSizeList
 +                                                height={height}
 +                                                itemCount={filtered.length}
 +                                                itemSize={35}
-                                                 width={width}>
++                                                width={width}
++                                            >
 +                                                {({ index, style }) => {
 +                                                    const { id, type, name } = filtered[index];
 +                                                    return (
 +                                                        <div
 +                                                            data-id={id}
 +                                                            style={style}
 +                                                            data-item="true"
 +                                                            data-type={type}
 +                                                            data-parent-path={name}
 +                                                            className={classNames(classes.row, getActiveClass(name))}
-                                                             key={id}>
++                                                            key={id}
++                                                        >
 +                                                            {getItemIcon(type, getActiveClass(name))}
 +                                                            <div className={classes.rowName}>{name}</div>
 +                                                            {getActiveClass(name) ? (
 +                                                                <SidePanelRightArrowIcon
 +                                                                    style={{ display: "inline", marginTop: "5px", marginLeft: "5px" }}
 +                                                                />
 +                                                            ) : null}
 +                                                        </div>
 +                                                    );
 +                                                }}
 +                                            </FixedSizeList>
 +                                        ) : (
 +                                            <div className={classes.rowEmpty}>No directories available</div>
 +                                        );
 +                                    }}
 +                                </AutoSizer>
 +                            ) : (
 +                                <div
 +                                    data-cy="collection-loader"
-                                     className={classes.row}>
++                                    className={classes.row}
++                                >
 +                                    <CircularProgress
 +                                        className={classes.loader}
 +                                        size={30}
 +                                    />
 +                                </div>
 +                            )}
 +                        </div>
 +                    </div>
 +                    <div
 +                        className={classes.rightPanel}
-                         data-cy="collection-files-right-panel">
++                        data-cy="collection-files-right-panel"
++                    >
 +                        <div className={classes.searchWrapper}>
 +                            <SearchInput
 +                                selfClearProp={rightKey}
 +                                label="Search"
 +                                value={rightSearch}
 +                                onSearch={setRightSearch}
 +                            />
 +                        </div>
 +                        {isWritable && (
 +                            <Button
 +                                className={classes.uploadButton}
 +                                data-cy="upload-button"
 +                                onClick={() => {
 +                                    onUploadDataClick(rightKey === leftKey ? undefined : rightKey);
 +                                }}
 +                                variant="contained"
 +                                color="primary"
-                                 size="small">
++                                size="small"
++                            >
 +                                <DownloadIcon className={classes.uploadIcon} />
 +                                Upload data
 +                            </Button>
 +                        )}
 +                        <div className={classes.dataWrapper}>
 +                            {rightData && !isLoading ? (
 +                                <AutoSizer defaultHeight={500}>
 +                                    {({ height, width }) => {
 +                                        const filtered = rightData.filter(({ name }) => name.indexOf(rightSearch) > -1);
 +                                        return !!filtered.length ? (
 +                                            <FixedSizeList
 +                                                height={height}
 +                                                itemCount={filtered.length}
 +                                                itemSize={35}
-                                                 width={width}>
++                                                width={width}
++                                            >
 +                                                {({ index, style }) => {
 +                                                    const { id, type, name, size } = filtered[index];
 +
 +                                                    return (
 +                                                        <div
 +                                                            style={style}
 +                                                            data-id={id}
 +                                                            data-item="true"
 +                                                            data-type={type}
 +                                                            data-subfolder-path={name}
 +                                                            className={classes.row}
-                                                             key={id}>
++                                                            key={id}
++                                                        >
 +                                                            <Checkbox
 +                                                                color="primary"
 +                                                                className={classes.rowSelection}
 +                                                                checked={collectionPanelFiles[id] ? collectionPanelFiles[id].value.selected : false}
 +                                                            />
 +                                                             
 +                                                            {getItemIcon(type, null)}
 +                                                            <div className={classes.rowName}>{name}</div>
 +                                                            <span
 +                                                                className={classes.rowName}
 +                                                                style={{
 +                                                                    marginLeft: "auto",
 +                                                                    marginRight: "1rem",
-                                                                 }}>
++                                                                }}
++                                                            >
 +                                                                {formatFileSize(size)}
 +                                                            </span>
 +                                                            <Tooltip
 +                                                                title="More options"
-                                                                 disableFocusListener>
++                                                                disableFocusListener
++                                                            >
 +                                                                <IconButton
 +                                                                    data-id="moreOptions"
 +                                                                    data-cy="file-item-options-btn"
-                                                                     className={classes.moreOptionsButton}>
-                                                                     <MoreOptionsIcon
++                                                                    className={classes.moreOptionsButton}
++                                                                >
++                                                                    <MoreHorizontalIcon
 +                                                                        data-id="moreOptions"
 +                                                                        className={classes.moreOptions}
 +                                                                    />
 +                                                                </IconButton>
 +                                                            </Tooltip>
 +                                                        </div>
 +                                                    );
 +                                                }}
 +                                            </FixedSizeList>
 +                                        ) : (
 +                                            <div className={classes.rowEmpty}>This collection is empty</div>
 +                                        );
 +                                    }}
 +                                </AutoSizer>
 +                            ) : (
 +                                <div className={classes.row}>
 +                                    <CircularProgress
 +                                        className={classes.loader}
 +                                        size={30}
 +                                    />
 +                                </div>
 +                            )}
 +                        </div>
 +                    </div>
                  </div>
              </div>
 -        </div>
 -    </div>
 -}));
 +        );
 +    })
 +);
diff --cc src/components/data-explorer/data-explorer.tsx
index 91d7dc77,dc097c39..ad5762df
--- a/src/components/data-explorer/data-explorer.tsx
+++ b/src/components/data-explorer/data-explorer.tsx
@@@ -7,17 -7,21 +7,17 @@@ import { Grid, Paper, Toolbar, StyleRul
  import { ColumnSelector } from "components/column-selector/column-selector";
  import { DataTable, DataColumns, DataTableFetchMode } from "components/data-table/data-table";
  import { DataColumn } from "components/data-table/data-column";
 -import { SearchInput } from 'components/search-input/search-input';
 +import { SearchInput } from "components/search-input/search-input";
  import { ArvadosTheme } from "common/custom-theme";
 -import { createTree } from 'models/tree';
 -import { DataTableFilters } from 'components/data-table-filters/data-table-filters-tree';
 -import {
 -    CloseIcon,
 -    IconType,
 -    MaximizeIcon,
 -    UnMaximizeIcon,
 -    MoreVerticalIcon
 -} from 'components/icon/icon';
 -import { PaperProps } from '@material-ui/core/Paper';
 -import { MPVPanelProps } from 'components/multi-panel-view/multi-panel-view';
++import { MultiselectToolbar } from "components/multiselect-toolbar/MultiselectToolbar";
++import { TCheckedList } from "components/data-table/data-table";
 +import { createTree } from "models/tree";
 +import { DataTableFilters } from "components/data-table-filters/data-table-filters-tree";
- import { CloseIcon, IconType, MaximizeIcon, UnMaximizeIcon, MoreOptionsIcon } from "components/icon/icon";
++import { CloseIcon, IconType, MaximizeIcon, UnMaximizeIcon, MoreVerticalIcon } from "components/icon/icon";
 +import { PaperProps } from "@material-ui/core/Paper";
 +import { MPVPanelProps } from "components/multi-panel-view/multi-panel-view";
- import { MultiselectToolbar } from "components/multiselect-toolbar/MultiselectToolbar";
- import { TCheckedList } from "components/data-table/data-table";
  
 -type CssRules = 'searchBox' | 'headerMenu' | "toolbar" | "footer" | "root" | 'moreOptionsButton' | 'title' | 'dataTable' | 'container';
 +type CssRules = "searchBox" | "headerMenu" | "toolbar" | "footer" | "root" | "moreOptionsButton" | "title" | "dataTable" | "container";
  
  const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
      searchBox: {
@@@ -155,182 -153,90 +155,194 @@@ export const DataExplorer = withStyles(
  
          render() {
              const {
 -                columns, onContextMenu, onFiltersChange, onSortToggle, extractKey,
 -                rowsPerPage, rowsPerPageOptions, onColumnToggle, searchLabel, searchValue, onSearch,
 -                items, itemsAvailable, onRowClick, onRowDoubleClick, classes,
 -                defaultViewIcon, defaultViewMessages, hideColumnSelector, actions, paperProps, hideSearchInput,
 -                paperKey, fetchMode, currentItemUuid, title,
 -                doHidePanel, doMaximizePanel, doUnMaximizePanel, panelName, panelMaximized, elementPath
 +                columns,
 +                onContextMenu,
 +                onFiltersChange,
 +                onSortToggle,
 +                extractKey,
 +                rowsPerPage,
 +                rowsPerPageOptions,
 +                onColumnToggle,
 +                searchLabel,
 +                searchValue,
 +                onSearch,
 +                items,
 +                itemsAvailable,
 +                onRowClick,
 +                onRowDoubleClick,
 +                classes,
 +                defaultViewIcon,
 +                defaultViewMessages,
 +                hideColumnSelector,
 +                actions,
 +                paperProps,
 +                hideSearchInput,
 +                paperKey,
 +                fetchMode,
 +                currentItemUuid,
 +                title,
 +                doHidePanel,
 +                doMaximizePanel,
 +                doUnMaximizePanel,
 +                panelName,
 +                panelMaximized,
 +                elementPath,
 +                toggleMSToolbar,
 +                setCheckedListOnStore,
 +                checkedList,
              } = this.props;
 -            return <Paper className={classes.root} {...paperProps} key={paperKey} data-cy={this.props["data-cy"]}>
 -                <Grid container direction="column" wrap="nowrap" className={classes.container}>
 -                    <div>
 -                        {title && <Grid item xs className={classes.title}>{title}</Grid>}
 -                        {
 -                            (!hideColumnSelector || !hideSearchInput || !!actions) &&
 -                            <Grid className={classes.headerMenu} item xs>
 -                                <Toolbar className={classes.toolbar}>
 -                                    {!hideSearchInput && <div className={classes.searchBox}>
 -                                        {!hideSearchInput && <SearchInput
 -                                            label={searchLabel}
 -                                            value={searchValue}
 -                                            selfClearProp={''}
 -                                            onSearch={onSearch} />}
 -                                    </div>}
 -                                    {actions}
 -                                    {!hideColumnSelector && <ColumnSelector
 -                                        columns={columns}
 -                                        onColumnToggle={onColumnToggle} />}
 -                                    { doUnMaximizePanel && panelMaximized &&
 -                                    <Tooltip title={`Unmaximize ${panelName || 'panel'}`} disableFocusListener>
 -                                        <IconButton onClick={doUnMaximizePanel}><UnMaximizeIcon /></IconButton>
 -                                    </Tooltip> }
 -                                    { doMaximizePanel && !panelMaximized &&
 -                                        <Tooltip title={`Maximize ${panelName || 'panel'}`} disableFocusListener>
 -                                            <IconButton onClick={doMaximizePanel}><MaximizeIcon /></IconButton>
 -                                        </Tooltip> }
 -                                    { doHidePanel &&
 -                                        <Tooltip title={`Close ${panelName || 'panel'}`} disableFocusListener>
 -                                            <IconButton disabled={panelMaximized} onClick={doHidePanel}><CloseIcon /></IconButton>
 -                                        </Tooltip> }
 -                                </Toolbar>
 -                            </Grid>
 -                        }
 -                    </div>
 -                <Grid item xs="auto" className={classes.dataTable}><DataTable
 -                    columns={this.props.contextMenuColumn ? [...columns, this.contextMenuColumn] : columns}
 -                    items={items}
 -                    onRowClick={(_, item: T) => onRowClick(item)}
 -                    onContextMenu={onContextMenu}
 -                    onRowDoubleClick={(_, item: T) => onRowDoubleClick(item)}
 -                    onFiltersChange={onFiltersChange}
 -                    onSortToggle={onSortToggle}
 -                    extractKey={extractKey}
 -                    working={this.state.showLoading}
 -                    defaultViewIcon={defaultViewIcon}
 -                    defaultViewMessages={defaultViewMessages}
 -                    currentItemUuid={currentItemUuid}
 -                    currentRoute={paperKey} /></Grid>
 -                <Grid item xs><Toolbar className={classes.footer}>
 -                    {
 -                        elementPath &&
 -                        <Grid container>
 -                            <span data-cy="element-path">
 -                                {elementPath}
 -                            </span>
 +            return (
 +                <Paper
 +                    className={classes.root}
 +                    {...paperProps}
 +                    key={paperKey}
-                     data-cy={this.props["data-cy"]}>
++                    data-cy={this.props["data-cy"]}
++                >
 +                    <Grid
 +                        container
 +                        direction="column"
 +                        wrap="nowrap"
-                         className={classes.container}>
++                        className={classes.container}
++                    >
 +                        <div>
 +                            {title && (
 +                                <Grid
 +                                    item
 +                                    xs
-                                     className={classes.title}>
++                                    className={classes.title}
++                                >
 +                                    {title}
 +                                </Grid>
 +                            )}
 +                            {(!hideColumnSelector || !hideSearchInput || !!actions) && (
 +                                <Grid
 +                                    className={classes.headerMenu}
 +                                    item
-                                     xs>
++                                    xs
++                                >
 +                                    <Toolbar className={classes.toolbar}>
 +                                        {!hideSearchInput && (
 +                                            <div className={classes.searchBox}>
 +                                                {!hideSearchInput && (
 +                                                    <SearchInput
 +                                                        label={searchLabel}
 +                                                        value={searchValue}
 +                                                        selfClearProp={""}
 +                                                        onSearch={onSearch}
 +                                                    />
 +                                                )}
 +                                            </div>
 +                                        )}
 +                                        {actions}
 +                                        {!hideColumnSelector && (
 +                                            <ColumnSelector
 +                                                columns={columns}
 +                                                onColumnToggle={onColumnToggle}
 +                                            />
 +                                        )}
 +                                        {doUnMaximizePanel && panelMaximized && (
 +                                            <Tooltip
 +                                                title={`Unmaximize ${panelName || "panel"}`}
-                                                 disableFocusListener>
++                                                disableFocusListener
++                                            >
 +                                                <IconButton onClick={doUnMaximizePanel}>
 +                                                    <UnMaximizeIcon />
 +                                                </IconButton>
 +                                            </Tooltip>
 +                                        )}
 +                                        {doMaximizePanel && !panelMaximized && (
 +                                            <Tooltip
 +                                                title={`Maximize ${panelName || "panel"}`}
-                                                 disableFocusListener>
++                                                disableFocusListener
++                                            >
 +                                                <IconButton onClick={doMaximizePanel}>
 +                                                    <MaximizeIcon />
 +                                                </IconButton>
 +                                            </Tooltip>
 +                                        )}
 +                                        {doHidePanel && (
 +                                            <Tooltip
 +                                                title={`Close ${panelName || "panel"}`}
-                                                 disableFocusListener>
++                                                disableFocusListener
++                                            >
 +                                                <IconButton
 +                                                    disabled={panelMaximized}
-                                                     onClick={doHidePanel}>
++                                                    onClick={doHidePanel}
++                                                >
 +                                                    <CloseIcon />
 +                                                </IconButton>
 +                                            </Tooltip>
 +                                        )}
 +                                    </Toolbar>
 +                                    <MultiselectToolbar />
 +                                </Grid>
 +                            )}
 +                        </div>
 +                        <Grid
 +                            item
 +                            xs="auto"
-                             className={classes.dataTable}>
++                            className={classes.dataTable}
++                        >
 +                            <DataTable
 +                                columns={this.props.contextMenuColumn ? [...columns, this.contextMenuColumn] : columns}
 +                                items={items}
 +                                onRowClick={(_, item: T) => onRowClick(item)}
 +                                onContextMenu={onContextMenu}
 +                                onRowDoubleClick={(_, item: T) => onRowDoubleClick(item)}
 +                                onFiltersChange={onFiltersChange}
 +                                onSortToggle={onSortToggle}
 +                                extractKey={extractKey}
 +                                working={this.state.showLoading}
 +                                defaultViewIcon={defaultViewIcon}
 +                                defaultViewMessages={defaultViewMessages}
 +                                currentItemUuid={currentItemUuid}
 +                                currentRoute={paperKey}
 +                                toggleMSToolbar={toggleMSToolbar}
 +                                setCheckedListOnStore={setCheckedListOnStore}
 +                                checkedList={checkedList}
 +                            />
 +                        </Grid>
 +                        <Grid
 +                            item
-                             xs>
++                            xs
++                        >
 +                            <Toolbar className={classes.footer}>
 +                                {elementPath && (
 +                                    <Grid container>
 +                                        <span data-cy="element-path">{elementPath}</span>
 +                                    </Grid>
 +                                )}
 +                                <Grid
 +                                    container={!elementPath}
-                                     justify="flex-end">
++                                    justify="flex-end"
++                                >
 +                                    {fetchMode === DataTableFetchMode.PAGINATED ? (
 +                                        <TablePagination
 +                                            count={itemsAvailable}
 +                                            rowsPerPage={rowsPerPage}
 +                                            rowsPerPageOptions={rowsPerPageOptions}
 +                                            page={this.props.page}
 +                                            onChangePage={this.changePage}
 +                                            onChangeRowsPerPage={this.changeRowsPerPage}
 +                                            // Disable next button on empty lists since that's not default behavior
 +                                            nextIconButtonProps={itemsAvailable > 0 ? {} : { disabled: true }}
 +                                            component="div"
 +                                        />
 +                                    ) : (
 +                                        <Button
 +                                            variant="text"
 +                                            size="medium"
-                                             onClick={this.loadMore}>
++                                            onClick={this.loadMore}
++                                        >
 +                                            Load more
 +                                        </Button>
 +                                    )}
 +                                </Grid>
 +                            </Toolbar>
                          </Grid>
 -                    }
 -                    <Grid container={!elementPath} justify="flex-end">
 -                        {fetchMode === DataTableFetchMode.PAGINATED ? <TablePagination
 -                            count={itemsAvailable}
 -                            rowsPerPage={rowsPerPage}
 -                            rowsPerPageOptions={rowsPerPageOptions}
 -                            page={this.props.page}
 -                            onChangePage={this.changePage}
 -                            onChangeRowsPerPage={this.changeRowsPerPage}
 -                            // Disable next button on empty lists since that's not default behavior
 -                            nextIconButtonProps={(itemsAvailable > 0) ? {} : {disabled: true}}
 -                            component="div" /> : <Button
 -                                variant="text"
 -                                size="medium"
 -                                onClick={this.loadMore}
 -                            >Load more</Button>}
                      </Grid>
 -                </Toolbar></Grid>
 -                </Grid>
 -            </Paper>;
 +                </Paper>
 +            );
          }
  
          changePage = (event: React.MouseEvent<HTMLButtonElement>, page: number) => {
@@@ -343,19 -249,13 +355,22 @@@
  
          loadMore = () => {
              this.props.onLoadMore(this.props.page + 1);
 -        }
 +        };
  
 -        renderContextMenuTrigger = (item: T) =>
 -            <Grid container justify="center">
 -                <Tooltip title="More options" disableFocusListener>
 -                    <IconButton className={this.props.classes.moreOptionsButton} onClick={event => this.props.onContextMenu(event, item)}>
 +        renderContextMenuTrigger = (item: T) => (
 +            <Grid
 +                container
-                 justify="center">
++                justify="center"
++            >
 +                <Tooltip
 +                    title="More options"
-                     disableFocusListener>
++                    disableFocusListener
++                >
 +                    <IconButton
 +                        className={this.props.classes.moreOptionsButton}
-                         onClick={event => this.props.onContextMenu(event, item)}>
-                         <MoreOptionsIcon />
++                        onClick={event => this.props.onContextMenu(event, item)}
++                    >
+                         <MoreVerticalIcon />
                      </IconButton>
                  </Tooltip>
              </Grid>
diff --cc src/index.tsx
index cddfb464,7cc18783..d2af0952
--- a/src/index.tsx
+++ b/src/index.tsx
@@@ -16,76 -16,57 +16,81 @@@ import { ApiToken } from "views-compone
  import { AddSession } from "views-components/add-session/add-session";
  import { initAuth, logout } from "store/auth/auth-action";
  import { createServices } from "services/services";
 -import { MuiThemeProvider } from '@material-ui/core/styles';
 -import { CustomTheme } from 'common/custom-theme';
 -import { fetchConfig } from 'common/config';
 -import servicesProvider from 'common/service-provider';
 -import { addMenuActionSet, ContextMenuKind } from 'views-components/context-menu/context-menu';
 +import { MuiThemeProvider } from "@material-ui/core/styles";
 +import { CustomTheme } from "common/custom-theme";
 +import { fetchConfig } from "common/config";
 +import servicesProvider from "common/service-provider";
 +import { addMenuActionSet, ContextMenuKind } from "views-components/context-menu/context-menu";
  import { rootProjectActionSet } from "views-components/context-menu/action-sets/root-project-action-set";
 -import { filterGroupActionSet, frozenActionSet, projectActionSet, readOnlyProjectActionSet } from "views-components/context-menu/action-sets/project-action-set";
 -import { resourceActionSet } from 'views-components/context-menu/action-sets/resource-action-set';
 +import {
 +    filterGroupActionSet,
 +    frozenActionSet,
 +    projectActionSet,
 +    readOnlyProjectActionSet,
 +} from "views-components/context-menu/action-sets/project-action-set";
 +import { resourceActionSet } from "views-components/context-menu/action-sets/resource-action-set";
  import { favoriteActionSet } from "views-components/context-menu/action-sets/favorite-action-set";
- import { collectionFilesActionSet, readOnlyCollectionFilesActionSet } from "views-components/context-menu/action-sets/collection-files-action-set";
 -import { collectionFilesActionSet, collectionFilesMultipleActionSet, readOnlyCollectionFilesActionSet, readOnlyCollectionFilesMultipleActionSet } from 'views-components/context-menu/action-sets/collection-files-action-set';
 -import { collectionDirectoryItemActionSet, collectionFileItemActionSet, readOnlyCollectionDirectoryItemActionSet, readOnlyCollectionFileItemActionSet } from 'views-components/context-menu/action-sets/collection-files-item-action-set';
 -import { collectionFilesNotSelectedActionSet } from 'views-components/context-menu/action-sets/collection-files-not-selected-action-set';
 -import { collectionActionSet, collectionAdminActionSet, oldCollectionVersionActionSet, readOnlyCollectionActionSet } from 'views-components/context-menu/action-sets/collection-action-set';
 -import { loadWorkbench } from 'store/workbench/workbench-actions';
 -import { Routes } from 'routes/routes';
++import {
++    collectionFilesActionSet,
++    collectionFilesMultipleActionSet,
++    readOnlyCollectionFilesActionSet,
++    readOnlyCollectionFilesMultipleActionSet,
++} from "views-components/context-menu/action-sets/collection-files-action-set";
 +import {
 +    collectionDirectoryItemActionSet,
 +    collectionFileItemActionSet,
 +    readOnlyCollectionDirectoryItemActionSet,
 +    readOnlyCollectionFileItemActionSet,
 +} from "views-components/context-menu/action-sets/collection-files-item-action-set";
 +import { collectionFilesNotSelectedActionSet } from "views-components/context-menu/action-sets/collection-files-not-selected-action-set";
 +import {
 +    collectionActionSet,
 +    collectionAdminActionSet,
 +    oldCollectionVersionActionSet,
 +    readOnlyCollectionActionSet,
 +} from "views-components/context-menu/action-sets/collection-action-set";
 +import { loadWorkbench } from "store/workbench/workbench-actions";
 +import { Routes } from "routes/routes";
  import { trashActionSet } from "views-components/context-menu/action-sets/trash-action-set";
 -import { ServiceRepository } from 'services/services';
 -import { initWebSocket } from 'websocket/websocket';
 -import { Config } from 'common/config';
 -import { addRouteChangeHandlers } from './routes/route-change-handlers';
 -import { setTokenDialogApiHost } from 'store/token-dialog/token-dialog-actions';
 +import { ServiceRepository } from "services/services";
 +import { initWebSocket } from "websocket/websocket";
 +import { Config } from "common/config";
 +import { addRouteChangeHandlers } from "./routes/route-change-handlers";
 +import { setTokenDialogApiHost } from "store/token-dialog/token-dialog-actions";
  import {
      processResourceActionSet,
      processResourceAdminActionSet,
 -    readOnlyProcessResourceActionSet
 -} from 'views-components/context-menu/action-sets/process-resource-action-set';
 -import { progressIndicatorActions } from 'store/progress-indicator/progress-indicator-actions';
 -import { trashedCollectionActionSet } from 'views-components/context-menu/action-sets/trashed-collection-action-set';
 -import { setBuildInfo } from 'store/app-info/app-info-actions';
 -import { getBuildInfo } from 'common/app-info';
 -import { DragDropContextProvider } from 'react-dnd';
 -import HTML5Backend from 'react-dnd-html5-backend';
 -import { initAdvancedFormProjectsTree } from 'store/search-bar/search-bar-actions';
 -import { repositoryActionSet } from 'views-components/context-menu/action-sets/repository-action-set';
 -import { sshKeyActionSet } from 'views-components/context-menu/action-sets/ssh-key-action-set';
 -import { keepServiceActionSet } from 'views-components/context-menu/action-sets/keep-service-action-set';
 -import { loadVocabulary } from 'store/vocabulary/vocabulary-actions';
 -import { virtualMachineActionSet } from 'views-components/context-menu/action-sets/virtual-machine-action-set';
 -import { userActionSet } from 'views-components/context-menu/action-sets/user-action-set';
 -import { apiClientAuthorizationActionSet } from 'views-components/context-menu/action-sets/api-client-authorization-action-set';
 -import { groupActionSet } from 'views-components/context-menu/action-sets/group-action-set';
 -import { groupMemberActionSet } from 'views-components/context-menu/action-sets/group-member-action-set';
 -import { linkActionSet } from 'views-components/context-menu/action-sets/link-action-set';
 -import { loadFileViewersConfig } from 'store/file-viewers/file-viewers-actions';
 -import { filterGroupAdminActionSet, frozenAdminActionSet, projectAdminActionSet } from 'views-components/context-menu/action-sets/project-admin-action-set';
 -import { permissionEditActionSet } from 'views-components/context-menu/action-sets/permission-edit-action-set';
 -import { workflowActionSet, readOnlyWorkflowActionSet } from 'views-components/context-menu/action-sets/workflow-action-set';
 +    readOnlyProcessResourceActionSet,
 +} from "views-components/context-menu/action-sets/process-resource-action-set";
 +import { progressIndicatorActions } from "store/progress-indicator/progress-indicator-actions";
 +import { trashedCollectionActionSet } from "views-components/context-menu/action-sets/trashed-collection-action-set";
 +import { setBuildInfo } from "store/app-info/app-info-actions";
 +import { getBuildInfo } from "common/app-info";
 +import { DragDropContextProvider } from "react-dnd";
 +import HTML5Backend from "react-dnd-html5-backend";
 +import { initAdvancedFormProjectsTree } from "store/search-bar/search-bar-actions";
 +import { repositoryActionSet } from "views-components/context-menu/action-sets/repository-action-set";
 +import { sshKeyActionSet } from "views-components/context-menu/action-sets/ssh-key-action-set";
 +import { keepServiceActionSet } from "views-components/context-menu/action-sets/keep-service-action-set";
 +import { loadVocabulary } from "store/vocabulary/vocabulary-actions";
 +import { virtualMachineActionSet } from "views-components/context-menu/action-sets/virtual-machine-action-set";
 +import { userActionSet } from "views-components/context-menu/action-sets/user-action-set";
 +import { apiClientAuthorizationActionSet } from "views-components/context-menu/action-sets/api-client-authorization-action-set";
 +import { groupActionSet } from "views-components/context-menu/action-sets/group-action-set";
 +import { groupMemberActionSet } from "views-components/context-menu/action-sets/group-member-action-set";
 +import { linkActionSet } from "views-components/context-menu/action-sets/link-action-set";
 +import { loadFileViewersConfig } from "store/file-viewers/file-viewers-actions";
 +import {
 +    filterGroupAdminActionSet,
 +    frozenAdminActionSet,
 +    projectAdminActionSet,
 +} from "views-components/context-menu/action-sets/project-admin-action-set";
 +import { permissionEditActionSet } from "views-components/context-menu/action-sets/permission-edit-action-set";
- import { workflowActionSet } from "views-components/context-menu/action-sets/workflow-action-set";
++import { workflowActionSet, readOnlyWorkflowActionSet } from "views-components/context-menu/action-sets/workflow-action-set";
  import { snackbarActions, SnackbarKind } from "store/snackbar/snackbar-actions";
 -import { openNotFoundDialog } from './store/not-found-panel/not-found-panel-action';
 -import { storeRedirects } from './common/redirect-to';
 -import { searchResultsActionSet } from 'views-components/context-menu/action-sets/search-results-action-set';
 +import { openNotFoundDialog } from "./store/not-found-panel/not-found-panel-action";
 +import { storeRedirects } from "./common/redirect-to";
 +import { searchResultsActionSet } from "views-components/context-menu/action-sets/search-results-action-set";
  
  console.log(`Starting arvados [${getBuildInfo()}]`);
  
diff --cc src/services/collection-service/collection-service.ts
index 4bd989e0,7e28c37b..de8f2587
--- a/src/services/collection-service/collection-service.ts
+++ b/src/services/collection-service/collection-service.ts
@@@ -12,22 -12,27 +12,28 @@@ import { TrashableResourceService } fro
  import { ApiActions } from "services/api/api-actions";
  import { Session } from "models/session";
  import { CommonService } from "services/common-service/common-service";
+ import { snakeCase } from "lodash";
+ import { CommonResourceServiceError } from "services/common-service/common-resource-service";
  
  export type UploadProgress = (fileId: number, loaded: number, total: number, currentTime: number) => void;
 -type CollectionPartialUpdateOrCreate =  Partial<CollectionResource> & Pick<CollectionResource, "uuid"> |
 -                                        Partial<CollectionResource> & Pick<CollectionResource, "ownerUuid">;
++type CollectionPartialUpdateOrCreate =
++    | (Partial<CollectionResource> & Pick<CollectionResource, "uuid">)
++    | (Partial<CollectionResource> & Pick<CollectionResource, "ownerUuid">);
  
 -export const emptyCollectionPdh = 'd41d8cd98f00b204e9800998ecf8427e+0';
 -export const SOURCE_DESTINATION_EQUAL_ERROR_MESSAGE = 'Source and destination cannot be the same';
 +export const emptyCollectionPdh = "d41d8cd98f00b204e9800998ecf8427e+0";
++export const SOURCE_DESTINATION_EQUAL_ERROR_MESSAGE = "Source and destination cannot be the same";
  
  export class CollectionService extends TrashableResourceService<CollectionResource> {
-     constructor(serverApi: AxiosInstance, private webdavClient: WebDAV, private authService: AuthService, actions: ApiActions) {
+     constructor(serverApi: AxiosInstance, private keepWebdavClient: WebDAV, private authService: AuthService, actions: ApiActions) {
          super(serverApi, "collections", actions, [
 -            'fileCount',
 -            'fileSizeTotal',
 -            'replicationConfirmed',
 -            'replicationConfirmedAt',
 -            'storageClassesConfirmed',
 -            'storageClassesConfirmedAt',
 -            'unsignedManifestText',
 -            'version',
 +            "fileCount",
 +            "fileSizeTotal",
 +            "replicationConfirmed",
 +            "replicationConfirmedAt",
 +            "storageClassesConfirmed",
 +            "storageClassesConfirmedAt",
 +            "unsignedManifestText",
 +            "version",
          ]);
      }
  
@@@ -72,22 -76,33 +78,33 @@@
          const payload = {
              collection: {
                  preserve_version: true,
+                 ...CommonService.mapKeys(snakeCase)(data),
+                 // Don't send uuid in payload when creating
+                 uuid: undefined,
              },
 -            replace_files: fileMap
 +            replace_files: fileMap,
          };
- 
-         return CommonService.defaultResponse(
-             this.serverApi.put<CollectionResource>(`/${this.resourceType}/${collectionUuid}`, payload),
-             this.actions,
-             true, // mapKeys
-             showErrors
-         );
+         if (data.uuid) {
+             return CommonService.defaultResponse(
 -                this.serverApi
 -                    .put<CollectionResource>(`/${this.resourceType}/${data.uuid}`, payload),
++                this.serverApi.put<CollectionResource>(`/${this.resourceType}/${data.uuid}`, payload),
+                 this.actions,
+                 true, // mapKeys
+                 showErrors
+             );
+         } else {
+             return CommonService.defaultResponse(
 -                this.serverApi
 -                    .post<CollectionResource>(`/${this.resourceType}`, payload),
++                this.serverApi.post<CollectionResource>(`/${this.resourceType}`, payload),
+                 this.actions,
+                 true, // mapKeys
+                 showErrors
+             );
+         }
      }
  
 -    async uploadFiles(collectionUuid: string, files: File[], onProgress?: UploadProgress, targetLocation: string = '') {
 -        if (collectionUuid === "" || files.length === 0) { return; }
 +    async uploadFiles(collectionUuid: string, files: File[], onProgress?: UploadProgress, targetLocation: string = "") {
 +        if (collectionUuid === "" || files.length === 0) {
 +            return;
 +        }
          // files have to be uploaded sequentially
          for (let idx = 0; idx < files.length; idx++) {
              await this.uploadFile(collectionUuid, files[idx], idx, onProgress, targetLocation);
@@@ -96,42 -111,36 +113,47 @@@
      }
  
      async renameFile(collectionUuid: string, collectionPdh: string, oldPath: string, newPath: string) {
-         return this.replaceFiles(collectionUuid, {
 -        return this.replaceFiles({uuid: collectionUuid}, {
--            [this.combineFilePath([newPath])]: `${collectionPdh}${this.combineFilePath([oldPath])}`,
-             [this.combineFilePath([oldPath])]: "",
 -            [this.combineFilePath([oldPath])]: '',
--        });
++        return this.replaceFiles(
++            { uuid: collectionUuid },
++            {
++                [this.combineFilePath([newPath])]: `${collectionPdh}${this.combineFilePath([oldPath])}`,
++                [this.combineFilePath([oldPath])]: "",
++            }
++        );
      }
  
      extendFileURL = (file: CollectionDirectory | CollectionFile) => {
-         const baseUrl = this.webdavClient.getBaseUrl().endsWith("/") ? this.webdavClient.getBaseUrl().slice(0, -1) : this.webdavClient.getBaseUrl();
 -        const baseUrl = this.keepWebdavClient.getBaseUrl().endsWith('/')
++        const baseUrl = this.keepWebdavClient.getBaseUrl().endsWith("/")
+             ? this.keepWebdavClient.getBaseUrl().slice(0, -1)
+             : this.keepWebdavClient.getBaseUrl();
          const apiToken = this.authService.getApiToken();
 -        const encodedApiToken = apiToken ? encodeURI(apiToken) : '';
 +        const encodedApiToken = apiToken ? encodeURI(apiToken) : "";
          const userApiToken = `/t=${encodedApiToken}/`;
 -        const splittedPrevFileUrl = file.url.split('/');
 -        const url = `${baseUrl}/${splittedPrevFileUrl[1]}${userApiToken}${splittedPrevFileUrl.slice(2).join('/')}`;
 +        const splittedPrevFileUrl = file.url.split("/");
 +        const url = `${baseUrl}/${splittedPrevFileUrl[1]}${userApiToken}${splittedPrevFileUrl.slice(2).join("/")}`;
          return {
              ...file,
 -            url
 +            url,
          };
 -    }
 +    };
  
      async getFileContents(file: CollectionFile) {
-         return (await this.webdavClient.get(`c=${file.id}`)).response;
+         return (await this.keepWebdavClient.get(`c=${file.id}`)).response;
      }
  
 -    private async uploadFile(collectionUuid: string, file: File, fileId: number, onProgress: UploadProgress = () => { return; }, targetLocation: string = '') {
 -        const fileURL = `c=${targetLocation !== '' ? targetLocation : collectionUuid}/${file.name}`.replace('//', '/');
 +    private async uploadFile(
 +        collectionUuid: string,
 +        file: File,
 +        fileId: number,
 +        onProgress: UploadProgress = () => {
 +            return;
 +        },
 +        targetLocation: string = ""
 +    ) {
 +        const fileURL = `c=${targetLocation !== "" ? targetLocation : collectionUuid}/${file.name}`.replace("//", "/");
          const requestConfig = {
              headers: {
 -                'Content-Type': 'text/octet-stream'
 +                "Content-Type": "text/octet-stream",
              },
              onUploadProgress: (e: ProgressEvent) => {
                  onProgress(fileId, e.loaded, e.total, Date.now());
@@@ -156,54 -165,64 +178,74 @@@
          const fileMap = optimizedFiles.reduce((obj, filePath) => {
              return {
                  ...obj,
 -                [this.combineFilePath([filePath])]: ''
 -            }
 -        }, {})
 +                [this.combineFilePath([filePath])]: "",
 +            };
 +        }, {});
  
-         return this.replaceFiles(collectionUuid, fileMap, showErrors);
 -        return this.replaceFiles({uuid: collectionUuid}, fileMap, showErrors);
++        return this.replaceFiles({ uuid: collectionUuid }, fileMap, showErrors);
      }
  
-     copyFiles(sourcePdh: string, files: string[], destinationCollectionUuid: string, destinationPath: string, showErrors?: boolean) {
 -    copyFiles(sourcePdh: string, files: string[], destinationCollection: CollectionPartialUpdateOrCreate, destinationPath: string, showErrors?: boolean) {
++    copyFiles(
++        sourcePdh: string,
++        files: string[],
++        destinationCollection: CollectionPartialUpdateOrCreate,
++        destinationPath: string,
++        showErrors?: boolean
++    ) {
          const fileMap = files.reduce((obj, sourceFile) => {
-             const sourceFileName = sourceFile.split("/").filter(Boolean).slice(-1).join("");
 -            const fileBasename = sourceFile.split('/').filter(Boolean).slice(-1).join("");
++            const fileBasename = sourceFile.split("/").filter(Boolean).slice(-1).join("");
              return {
                  ...obj,
-                 [this.combineFilePath([destinationPath, sourceFileName])]: `${sourcePdh}${this.combineFilePath([sourceFile])}`,
 -                [this.combineFilePath([destinationPath, fileBasename])]: `${sourcePdh}${this.combineFilePath([sourceFile])}`
++                [this.combineFilePath([destinationPath, fileBasename])]: `${sourcePdh}${this.combineFilePath([sourceFile])}`,
              };
          }, {});
  
-         return this.replaceFiles(destinationCollectionUuid, fileMap, showErrors);
+         return this.replaceFiles(destinationCollection, fileMap, showErrors);
      }
  
 -    moveFiles(sourceUuid: string, sourcePdh: string, files: string[], destinationCollection: CollectionPartialUpdateOrCreate, destinationPath: string, showErrors?: boolean) {
 +    moveFiles(
 +        sourceUuid: string,
 +        sourcePdh: string,
 +        files: string[],
-         destinationCollectionUuid: string,
++        destinationCollection: CollectionPartialUpdateOrCreate,
 +        destinationPath: string,
 +        showErrors?: boolean
 +    ) {
-         if (sourceUuid === destinationCollectionUuid) {
+         if (sourceUuid === destinationCollection.uuid) {
+             let errors: CommonResourceServiceError[] = [];
              const fileMap = files.reduce((obj, sourceFile) => {
-                 const sourceFileName = sourceFile.split("/").filter(Boolean).slice(-1).join("");
-                 return {
-                     ...obj,
-                     [this.combineFilePath([destinationPath, sourceFileName])]: `${sourcePdh}${this.combineFilePath([sourceFile])}`,
-                     [this.combineFilePath([sourceFile])]: "",
-                 };
 -                const fileBasename = sourceFile.split('/').filter(Boolean).slice(-1).join("");
++                const fileBasename = sourceFile.split("/").filter(Boolean).slice(-1).join("");
+                 const fileDestinationPath = this.combineFilePath([destinationPath, fileBasename]);
+                 const fileSourcePath = this.combineFilePath([sourceFile]);
+                 const fileSourceUri = `${sourcePdh}${fileSourcePath}`;
+ 
 -
+                 if (fileDestinationPath !== fileSourcePath) {
+                     return {
+                         ...obj,
+                         [fileDestinationPath]: fileSourceUri,
 -                        [fileSourcePath]: '',
++                        [fileSourcePath]: "",
+                     };
+                 } else {
+                     errors.push(CommonResourceServiceError.SOURCE_DESTINATION_CANNOT_BE_SAME);
+                     return obj;
+                 }
              }, {});
  
-             return this.replaceFiles(sourceUuid, fileMap, showErrors);
+             if (errors.length === 0) {
 -                return this.replaceFiles({uuid: sourceUuid}, fileMap, showErrors)
++                return this.replaceFiles({ uuid: sourceUuid }, fileMap, showErrors);
+             } else {
 -                return Promise.reject({errors});
++                return Promise.reject({ errors });
+             }
          } else {
-             return this.copyFiles(sourcePdh, files, destinationCollectionUuid, destinationPath, showErrors).then(() => {
 -            return this.copyFiles(sourcePdh, files, destinationCollection, destinationPath, showErrors)
 -                .then(() => {
 -                    return this.deleteFiles(sourceUuid, files, showErrors);
 -                });
++            return this.copyFiles(sourcePdh, files, destinationCollection, destinationPath, showErrors).then(() => {
 +                return this.deleteFiles(sourceUuid, files, showErrors);
 +            });
          }
      }
  
      createDirectory(collectionUuid: string, path: string, showErrors?: boolean) {
 -        const fileMap = {[this.combineFilePath([path])]: emptyCollectionPdh};
 +        const fileMap = { [this.combineFilePath([path])]: emptyCollectionPdh };
  
-         return this.replaceFiles(collectionUuid, fileMap, showErrors);
 -        return this.replaceFiles({uuid: collectionUuid}, fileMap, showErrors);
++        return this.replaceFiles({ uuid: collectionUuid }, fileMap, showErrors);
      }
 -
  }
diff --cc src/store/context-menu/context-menu-actions.ts
index 6fc5d56f,4abfb372..bd93649e
--- a/src/store/context-menu/context-menu-actions.ts
+++ b/src/store/context-menu/context-menu-actions.ts
@@@ -2,31 -2,32 +2,32 @@@
  //
  // SPDX-License-Identifier: AGPL-3.0
  
 -import { unionize, ofType, UnionOf } from 'common/unionize';
 +import { unionize, ofType, UnionOf } from "common/unionize";
  import { ContextMenuPosition } from "./context-menu-reducer";
 -import { ContextMenuKind } from 'views-components/context-menu/context-menu';
 -import { Dispatch } from 'redux';
 -import { RootState } from 'store/store';
 -import { getResource, getResourceWithEditableStatus } from '../resources/resources';
 -import { UserResource } from 'models/user';
 -import { isSidePanelTreeCategory } from 'store/side-panel-tree/side-panel-tree-actions';
 -import { extractUuidKind, ResourceKind, EditableResource, Resource } from 'models/resource';
 -import { Process } from 'store/processes/process';
 -import { RepositoryResource } from 'models/repositories';
 -import { SshKeyResource } from 'models/ssh-key';
 -import { VirtualMachinesResource } from 'models/virtual-machines';
 -import { KeepServiceResource } from 'models/keep-services';
 -import { ProcessResource } from 'models/process';
 -import { CollectionResource } from 'models/collection';
 -import { GroupClass, GroupResource } from 'models/group';
 -import { GroupContentsResource } from 'services/groups-service/groups-service';
 -import { LinkResource } from 'models/link';
 -import { resourceIsFrozen } from 'common/frozen-resources';
 -import { ProjectResource } from 'models/project';
 -import { filterCollectionFilesBySelection } from 'store/collection-panel/collection-panel-files/collection-panel-files-state';
 +import { ContextMenuKind } from "views-components/context-menu/context-menu";
 +import { Dispatch } from "redux";
 +import { RootState } from "store/store";
 +import { getResource, getResourceWithEditableStatus } from "../resources/resources";
 +import { UserResource } from "models/user";
 +import { isSidePanelTreeCategory } from "store/side-panel-tree/side-panel-tree-actions";
 +import { extractUuidKind, ResourceKind, EditableResource, Resource } from "models/resource";
 +import { Process } from "store/processes/process";
 +import { RepositoryResource } from "models/repositories";
 +import { SshKeyResource } from "models/ssh-key";
 +import { VirtualMachinesResource } from "models/virtual-machines";
 +import { KeepServiceResource } from "models/keep-services";
 +import { ProcessResource } from "models/process";
 +import { CollectionResource } from "models/collection";
 +import { GroupClass, GroupResource } from "models/group";
 +import { GroupContentsResource } from "services/groups-service/groups-service";
 +import { LinkResource } from "models/link";
 +import { resourceIsFrozen } from "common/frozen-resources";
 +import { ProjectResource } from "models/project";
++import { filterCollectionFilesBySelection } from "store/collection-panel/collection-panel-files/collection-panel-files-state";
  
  export const contextMenuActions = unionize({
 -    OPEN_CONTEXT_MENU: ofType<{ position: ContextMenuPosition, resource: ContextMenuResource }>(),
 -    CLOSE_CONTEXT_MENU: ofType<{}>()
 +    OPEN_CONTEXT_MENU: ofType<{ position: ContextMenuPosition; resource: ContextMenuResource }>(),
 +    CLOSE_CONTEXT_MENU: ofType<{}>(),
  });
  
  export type ContextMenuAction = UnionOf<typeof contextMenuActions>;
diff --cc src/store/processes/processes-actions.ts
index 6d6a48b7,b6ff4b71..25cb8561
--- a/src/store/processes/processes-actions.ts
+++ b/src/store/processes/processes-actions.ts
@@@ -24,12 -24,9 +24,13 @@@ import { CommandOutputParameter } from 
  import { ContainerResource } from "models/container";
  import { ContainerRequestResource, ContainerRequestState } from "models/container-request";
  import { FilterBuilder } from "services/api/filter-builder";
 +import { selectedToArray } from "components/multiselect-toolbar/MultiselectToolbar";
 +import { Resource, ResourceKind } from "models/resource";
 +import { ContextMenuResource } from "store/context-menu/context-menu-actions";
+ import { CommonResourceServiceError, getCommonResourceServiceError } from "services/common-service/common-resource-service";
  
 -export const loadProcess = (containerRequestUuid: string) =>
 +export const loadProcess =
 +    (containerRequestUuid: string) =>
      async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise<Process | undefined> => {
          let containerRequest: ContainerRequestResource | undefined = undefined;
          try {
@@@ -116,53 -112,57 +117,54 @@@ const containerFieldsNoMounts = 
      "scheduling_parameters",
      "started_at",
      "state",
+     "subrequests_cost",
      "uuid",
 -]
 +];
  
 -export const cancelRunningWorkflow = (uuid: string) =>
 -    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
 -        try {
 -            const process = await services.containerRequestService.update(uuid, { priority: 0 });
 -            dispatch<any>(updateResources([process]));
 -            if (process.containerUuid) {
 -                const container = await services.containerService.get(process.containerUuid, false);
 -                dispatch<any>(updateResources([container]));
 -            }
 -            return process;
 -        } catch (e) {
 -            throw new Error('Could not cancel the process.');
 +export const cancelRunningWorkflow = (uuid: string) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
 +    try {
 +        const process = await services.containerRequestService.update(uuid, { priority: 0 });
 +        dispatch<any>(updateResources([process]));
 +        if (process.containerUuid) {
 +            const container = await services.containerService.get(process.containerUuid, false);
 +            dispatch<any>(updateResources([container]));
          }
 -    };
 +        return process;
 +    } catch (e) {
 +        throw new Error("Could not cancel the process.");
 +    }
 +};
  
 -export const resumeOnHoldWorkflow = (uuid: string) =>
 -    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
 -        try {
 -            const process = await services.containerRequestService.update(uuid, { priority: 500 });
 -            dispatch<any>(updateResources([process]));
 -            if (process.containerUuid) {
 -                const container = await services.containerService.get(process.containerUuid, false);
 -                dispatch<any>(updateResources([container]));
 -            }
 -            return process;
 -        } catch (e) {
 -            throw new Error('Could not resume the process.');
 +export const resumeOnHoldWorkflow = (uuid: string) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
 +    try {
 +        const process = await services.containerRequestService.update(uuid, { priority: 500 });
 +        dispatch<any>(updateResources([process]));
 +        if (process.containerUuid) {
 +            const container = await services.containerService.get(process.containerUuid, false);
 +            dispatch<any>(updateResources([container]));
          }
 -    };
 +        return process;
 +    } catch (e) {
 +        throw new Error("Could not resume the process.");
 +    }
 +};
  
 -export const startWorkflow = (uuid: string) =>
 -    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
 -        try {
 -            const process = await services.containerRequestService.update(uuid, { state: ContainerRequestState.COMMITTED });
 -            if (process) {
 -                dispatch<any>(updateResources([process]));
 -                dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Process started', hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
 -            } else {
 -                dispatch<any>(snackbarActions.OPEN_SNACKBAR({ message: `Failed to start process`, kind: SnackbarKind.ERROR }));
 -            }
 -        } catch (e) {
 +export const startWorkflow = (uuid: string) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
 +    try {
 +        const process = await services.containerRequestService.update(uuid, { state: ContainerRequestState.COMMITTED });
 +        if (process) {
 +            dispatch<any>(updateResources([process]));
 +            dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Process started", hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
 +        } else {
              dispatch<any>(snackbarActions.OPEN_SNACKBAR({ message: `Failed to start process`, kind: SnackbarKind.ERROR }));
          }
 -    };
 +    } catch (e) {
 +        dispatch<any>(snackbarActions.OPEN_SNACKBAR({ message: `Failed to start process`, kind: SnackbarKind.ERROR }));
 +    }
 +};
  
 -export const reRunProcess = (processUuid: string, workflowUuid: string) =>
 -    (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
 +export const reRunProcess =
 +    (processUuid: string, workflowUuid: string) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
          const process = getResource<any>(processUuid)(getState().resources);
          const workflows = getState().runProcessPanel.searchWorkflows;
          const workflow = workflows.find(workflow => workflow.uuid === workflowUuid);
@@@ -272,61 -262,48 +274,70 @@@ export const getInputCollectionMounts 
  };
  
  export const getOutputParameters = (data: any): CommandOutputParameter[] => {
 -    if (!data || !data.mounts || !data.mounts[MOUNT_PATH_CWL_WORKFLOW]) { return []; }
 +    if (!data || !data.mounts || !data.mounts[MOUNT_PATH_CWL_WORKFLOW]) {
 +        return [];
 +    }
      const outputs = getWorkflowOutputs(data.mounts[MOUNT_PATH_CWL_WORKFLOW].content);
 -    return outputs ? outputs.map(
 -        (it: any) => (
 -            {
 -                type: it.type,
 -                id: it.id,
 -                label: it.label,
 -                doc: it.doc
 -            }
 -        )
 -    ) : [];
 +    return outputs
 +        ? outputs.map((it: any) => ({
 +              type: it.type,
 +              id: it.id,
 +              label: it.label,
 +              doc: it.doc,
 +          }))
 +        : [];
  };
  
 -export const openRemoveProcessDialog = (uuid: string) =>
 -    (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
 -        dispatch(dialogActions.OPEN_DIALOG({
 -            id: REMOVE_PROCESS_DIALOG,
 -            data: {
 -                title: 'Remove process permanently',
 -                text: 'Are you sure you want to remove this process?',
 -                confirmButtonLabel: 'Remove',
 -                uuid
 -            }
 -        }));
 +export const openRemoveProcessDialog =
 +    (resource: ContextMenuResource, numOfProcesses: Number) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
 +        const confirmationText =
 +            numOfProcesses === 1
 +                ? "Are you sure you want to remove this process?"
 +                : `Are you sure you want to remove these ${numOfProcesses} processes?`;
 +        const titleText = numOfProcesses === 1 ? "Remove process permanently" : "Remove processes permanently";
 +
 +        dispatch(
 +            dialogActions.OPEN_DIALOG({
 +                id: REMOVE_PROCESS_DIALOG,
 +                data: {
 +                    title: titleText,
 +                    text: confirmationText,
 +                    confirmButtonLabel: "Remove",
 +                    uuid: resource.uuid,
 +                    resource,
 +                },
 +            })
 +        );
      };
  
 -export const REMOVE_PROCESS_DIALOG = 'removeProcessDialog';
 +export const REMOVE_PROCESS_DIALOG = "removeProcessDialog";
  
 -export const removeProcessPermanently = (uuid: string) =>
 -    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
 +export const removeProcessPermanently = (uuid: string) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
 +    const resource = getState().dialog.removeProcessDialog.data.resource;
 +    const checkedList = getState().multiselect.checkedList;
 +
 +    const uuidsToRemove: string[] = resource.isSingle ? [resource.uuid] : selectedToArray(checkedList);
 +
 +    //if no items in checkedlist, default to normal context menu behavior
 +    if (!uuidsToRemove.length) uuidsToRemove.push(uuid);
 +
 +    const processesToRemove = uuidsToRemove
 +        .map(uuid => getResource(uuid)(getState().resources) as Resource)
 +        .filter(resource => resource.kind === ResourceKind.PROCESS);
 +
 +    for (const process of processesToRemove) {
-         dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Removing ...", kind: SnackbarKind.INFO }));
-         await services.containerRequestService.delete(process.uuid);
-         dispatch(projectPanelActions.REQUEST_ITEMS());
-         dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Removed.", hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
+         try {
 -            dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removing ...', kind: SnackbarKind.INFO }));
++            dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Removing ...", kind: SnackbarKind.INFO }));
+             await services.containerRequestService.delete(uuid, false);
+             dispatch(projectPanelActions.REQUEST_ITEMS());
 -            dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removed.', hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
++            dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Removed.", hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
+         } catch (e) {
+             const error = getCommonResourceServiceError(e);
+             if (error === CommonResourceServiceError.PERMISSION_ERROR_FORBIDDEN) {
+                 dispatch(snackbarActions.OPEN_SNACKBAR({ message: `Access denied`, hideDuration: 2000, kind: SnackbarKind.ERROR }));
+             } else {
+                 dispatch(snackbarActions.OPEN_SNACKBAR({ message: `Deletion failed`, hideDuration: 2000, kind: SnackbarKind.ERROR }));
+             }
+         }
 -    };
 +    }
 +};
diff --cc src/store/store.ts
index f4b82cb9,913207c3..e0af6047
--- a/src/store/store.ts
+++ b/src/store/store.ts
@@@ -2,90 -2,82 +2,91 @@@
  //
  // SPDX-License-Identifier: AGPL-3.0
  
- import { createStore, applyMiddleware, compose, Middleware, combineReducers, Store, Action, Dispatch } from 'redux';
- import { routerMiddleware, routerReducer } from 'react-router-redux';
- import thunkMiddleware from 'redux-thunk';
- import { History } from 'history';
- import { handleRedirects } from '../common/redirect-to';
- 
- import { authReducer } from './auth/auth-reducer';
- import { authMiddleware } from './auth/auth-middleware';
- import { dataExplorerReducer } from './data-explorer/data-explorer-reducer';
- import { detailsPanelReducer } from './details-panel/details-panel-reducer';
- import { contextMenuReducer } from './context-menu/context-menu-reducer';
- import { reducer as formReducer } from 'redux-form';
- import { favoritesReducer } from './favorites/favorites-reducer';
- import { snackbarReducer } from './snackbar/snackbar-reducer';
- import { collectionPanelFilesReducer } from './collection-panel/collection-panel-files/collection-panel-files-reducer';
- import { dataExplorerMiddleware } from './data-explorer/data-explorer-middleware';
- import { FAVORITE_PANEL_ID } from './favorite-panel/favorite-panel-action';
- import { PROJECT_PANEL_ID } from './project-panel/project-panel-action';
- import { ProjectPanelMiddlewareService } from './project-panel/project-panel-middleware-service';
- import { FavoritePanelMiddlewareService } from './favorite-panel/favorite-panel-middleware-service';
- import { AllProcessesPanelMiddlewareService } from './all-processes-panel/all-processes-panel-middleware-service';
- import { collectionPanelReducer } from './collection-panel/collection-panel-reducer';
- import { dialogReducer } from './dialog/dialog-reducer';
- import { ServiceRepository } from 'services/services';
- import { treePickerReducer, treePickerSearchReducer } from './tree-picker/tree-picker-reducer';
- import { treePickerSearchMiddleware } from './tree-picker/tree-picker-middleware';
- import { resourcesReducer } from 'store/resources/resources-reducer';
- import { propertiesReducer } from './properties/properties-reducer';
- import { fileUploaderReducer } from './file-uploader/file-uploader-reducer';
- import { TrashPanelMiddlewareService } from 'store/trash-panel/trash-panel-middleware-service';
- import { TRASH_PANEL_ID } from 'store/trash-panel/trash-panel-action';
- import { processLogsPanelReducer } from './process-logs-panel/process-logs-panel-reducer';
- import { processPanelReducer } from 'store/process-panel/process-panel-reducer';
- import { SHARED_WITH_ME_PANEL_ID } from 'store/shared-with-me-panel/shared-with-me-panel-actions';
- import { SharedWithMeMiddlewareService } from './shared-with-me-panel/shared-with-me-middleware-service';
- import { progressIndicatorReducer } from './progress-indicator/progress-indicator-reducer';
- import { runProcessPanelReducer } from 'store/run-process-panel/run-process-panel-reducer';
- import { WorkflowMiddlewareService } from './workflow-panel/workflow-middleware-service';
- import { WORKFLOW_PANEL_ID } from './workflow-panel/workflow-panel-actions';
- import { appInfoReducer } from 'store/app-info/app-info-reducer';
- import { searchBarReducer } from './search-bar/search-bar-reducer';
- import { SEARCH_RESULTS_PANEL_ID } from 'store/search-results-panel/search-results-panel-actions';
- import { SearchResultsMiddlewareService } from './search-results-panel/search-results-middleware-service';
- import { virtualMachinesReducer } from 'store/virtual-machines/virtual-machines-reducer';
- import { repositoriesReducer } from 'store/repositories/repositories-reducer';
- import { keepServicesReducer } from 'store/keep-services/keep-services-reducer';
- import { UserMiddlewareService } from 'store/users/user-panel-middleware-service';
- import { USERS_PANEL_ID } from 'store/users/users-actions';
- import { UserProfileGroupsMiddlewareService } from 'store/user-profile/user-profile-groups-middleware-service';
- import { USER_PROFILE_PANEL_ID } from 'store/user-profile/user-profile-actions';
- import { GroupsPanelMiddlewareService } from 'store/groups-panel/groups-panel-middleware-service';
- import { GROUPS_PANEL_ID } from 'store/groups-panel/groups-panel-actions';
- import { GroupDetailsPanelMembersMiddlewareService } from 'store/group-details-panel/group-details-panel-members-middleware-service';
- import { GroupDetailsPanelPermissionsMiddlewareService } from 'store/group-details-panel/group-details-panel-permissions-middleware-service';
- import { GROUP_DETAILS_MEMBERS_PANEL_ID, GROUP_DETAILS_PERMISSIONS_PANEL_ID } from 'store/group-details-panel/group-details-panel-actions';
- import { LINK_PANEL_ID } from 'store/link-panel/link-panel-actions';
- import { LinkMiddlewareService } from 'store/link-panel/link-panel-middleware-service';
- import { API_CLIENT_AUTHORIZATION_PANEL_ID } from 'store/api-client-authorizations/api-client-authorizations-actions';
- import { ApiClientAuthorizationMiddlewareService } from 'store/api-client-authorizations/api-client-authorizations-middleware-service';
- import { PublicFavoritesMiddlewareService } from 'store/public-favorites-panel/public-favorites-middleware-service';
- import { PUBLIC_FAVORITE_PANEL_ID } from 'store/public-favorites-panel/public-favorites-action';
- import { publicFavoritesReducer } from 'store/public-favorites/public-favorites-reducer';
- import { linkAccountPanelReducer } from './link-account-panel/link-account-panel-reducer';
- import { CollectionsWithSameContentAddressMiddlewareService } from 'store/collections-content-address-panel/collections-content-address-middleware-service';
- import { COLLECTIONS_CONTENT_ADDRESS_PANEL_ID } from 'store/collections-content-address-panel/collections-content-address-panel-actions';
- import { ownerNameReducer } from 'store/owner-name/owner-name-reducer';
- import { SubprocessMiddlewareService } from 'store/subprocess-panel/subprocess-panel-middleware-service';
- import { SUBPROCESS_PANEL_ID } from 'store/subprocess-panel/subprocess-panel-actions';
- import { ALL_PROCESSES_PANEL_ID } from './all-processes-panel/all-processes-panel-action';
- import { Config } from 'common/config';
- import { pluginConfig } from 'plugins';
- import { MiddlewareListReducer } from 'common/plugintypes';
- import { tooltipsMiddleware } from './tooltips/tooltips-middleware';
- import { sidePanelReducer } from './side-panel/side-panel-reducer';
- import { bannerReducer } from './banner/banner-reducer';
- import { multiselectReducer } from './multiselect/multiselect-reducer';
 -import { createStore, applyMiddleware, Middleware, combineReducers, Store, Action, Dispatch } from 'redux';
++import { createStore, applyMiddleware, compose, Middleware, combineReducers, Store, Action, Dispatch } from "redux";
+ import { routerMiddleware, routerReducer } from "react-router-redux";
 -import thunkMiddleware from 'redux-thunk';
++import thunkMiddleware from "redux-thunk";
+ import { History } from "history";
 -import { handleRedirects } from '../common/redirect-to';
++import { handleRedirects } from "../common/redirect-to";
+ 
+ import { authReducer } from "./auth/auth-reducer";
+ import { authMiddleware } from "./auth/auth-middleware";
 -import { dataExplorerReducer } from './data-explorer/data-explorer-reducer';
 -import { detailsPanelReducer } from './details-panel/details-panel-reducer';
 -import { contextMenuReducer } from './context-menu/context-menu-reducer';
 -import { reducer as formReducer } from 'redux-form';
 -import { favoritesReducer } from './favorites/favorites-reducer';
 -import { snackbarReducer } from './snackbar/snackbar-reducer';
 -import { collectionPanelFilesReducer } from './collection-panel/collection-panel-files/collection-panel-files-reducer';
++import { dataExplorerReducer } from "./data-explorer/data-explorer-reducer";
++import { detailsPanelReducer } from "./details-panel/details-panel-reducer";
++import { contextMenuReducer } from "./context-menu/context-menu-reducer";
++import { reducer as formReducer } from "redux-form";
++import { favoritesReducer } from "./favorites/favorites-reducer";
++import { snackbarReducer } from "./snackbar/snackbar-reducer";
++import { collectionPanelFilesReducer } from "./collection-panel/collection-panel-files/collection-panel-files-reducer";
+ import { dataExplorerMiddleware } from "./data-explorer/data-explorer-middleware";
+ import { FAVORITE_PANEL_ID } from "./favorite-panel/favorite-panel-action";
+ import { PROJECT_PANEL_ID } from "./project-panel/project-panel-action";
+ import { ProjectPanelMiddlewareService } from "./project-panel/project-panel-middleware-service";
+ import { FavoritePanelMiddlewareService } from "./favorite-panel/favorite-panel-middleware-service";
+ import { AllProcessesPanelMiddlewareService } from "./all-processes-panel/all-processes-panel-middleware-service";
 -import { collectionPanelReducer } from './collection-panel/collection-panel-reducer';
 -import { dialogReducer } from './dialog/dialog-reducer';
++import { collectionPanelReducer } from "./collection-panel/collection-panel-reducer";
++import { dialogReducer } from "./dialog/dialog-reducer";
+ import { ServiceRepository } from "services/services";
 -import { treePickerReducer, treePickerSearchReducer } from './tree-picker/tree-picker-reducer';
 -import { treePickerSearchMiddleware } from './tree-picker/tree-picker-middleware';
 -import { resourcesReducer } from 'store/resources/resources-reducer';
 -import { propertiesReducer } from './properties/properties-reducer';
 -import { fileUploaderReducer } from './file-uploader/file-uploader-reducer';
++import { treePickerReducer, treePickerSearchReducer } from "./tree-picker/tree-picker-reducer";
++import { treePickerSearchMiddleware } from "./tree-picker/tree-picker-middleware";
++import { resourcesReducer } from "store/resources/resources-reducer";
++import { propertiesReducer } from "./properties/properties-reducer";
++import { fileUploaderReducer } from "./file-uploader/file-uploader-reducer";
+ import { TrashPanelMiddlewareService } from "store/trash-panel/trash-panel-middleware-service";
+ import { TRASH_PANEL_ID } from "store/trash-panel/trash-panel-action";
 -import { processLogsPanelReducer } from './process-logs-panel/process-logs-panel-reducer';
 -import { processPanelReducer } from 'store/process-panel/process-panel-reducer';
 -import { SHARED_WITH_ME_PANEL_ID } from 'store/shared-with-me-panel/shared-with-me-panel-actions';
 -import { SharedWithMeMiddlewareService } from './shared-with-me-panel/shared-with-me-middleware-service';
 -import { progressIndicatorReducer } from './progress-indicator/progress-indicator-reducer';
 -import { runProcessPanelReducer } from 'store/run-process-panel/run-process-panel-reducer';
 -import { WorkflowMiddlewareService } from './workflow-panel/workflow-middleware-service';
 -import { WORKFLOW_PANEL_ID } from './workflow-panel/workflow-panel-actions';
 -import { appInfoReducer } from 'store/app-info/app-info-reducer';
 -import { searchBarReducer } from './search-bar/search-bar-reducer';
 -import { SEARCH_RESULTS_PANEL_ID } from 'store/search-results-panel/search-results-panel-actions';
 -import { SearchResultsMiddlewareService } from './search-results-panel/search-results-middleware-service';
++import { processLogsPanelReducer } from "./process-logs-panel/process-logs-panel-reducer";
++import { processPanelReducer } from "store/process-panel/process-panel-reducer";
++import { SHARED_WITH_ME_PANEL_ID } from "store/shared-with-me-panel/shared-with-me-panel-actions";
++import { SharedWithMeMiddlewareService } from "./shared-with-me-panel/shared-with-me-middleware-service";
++import { progressIndicatorReducer } from "./progress-indicator/progress-indicator-reducer";
++import { runProcessPanelReducer } from "store/run-process-panel/run-process-panel-reducer";
++import { WorkflowMiddlewareService } from "./workflow-panel/workflow-middleware-service";
++import { WORKFLOW_PANEL_ID } from "./workflow-panel/workflow-panel-actions";
++import { appInfoReducer } from "store/app-info/app-info-reducer";
++import { searchBarReducer } from "./search-bar/search-bar-reducer";
++import { SEARCH_RESULTS_PANEL_ID } from "store/search-results-panel/search-results-panel-actions";
++import { SearchResultsMiddlewareService } from "./search-results-panel/search-results-middleware-service";
+ import { virtualMachinesReducer } from "store/virtual-machines/virtual-machines-reducer";
 -import { repositoriesReducer } from 'store/repositories/repositories-reducer';
 -import { keepServicesReducer } from 'store/keep-services/keep-services-reducer';
 -import { UserMiddlewareService } from 'store/users/user-panel-middleware-service';
 -import { USERS_PANEL_ID } from 'store/users/users-actions';
 -import { UserProfileGroupsMiddlewareService } from 'store/user-profile/user-profile-groups-middleware-service';
 -import { USER_PROFILE_PANEL_ID } from 'store/user-profile/user-profile-actions'
 -import { GroupsPanelMiddlewareService } from 'store/groups-panel/groups-panel-middleware-service';
 -import { GROUPS_PANEL_ID } from 'store/groups-panel/groups-panel-actions';
 -import { GroupDetailsPanelMembersMiddlewareService } from 'store/group-details-panel/group-details-panel-members-middleware-service';
 -import { GroupDetailsPanelPermissionsMiddlewareService } from 'store/group-details-panel/group-details-panel-permissions-middleware-service';
 -import { GROUP_DETAILS_MEMBERS_PANEL_ID, GROUP_DETAILS_PERMISSIONS_PANEL_ID } from 'store/group-details-panel/group-details-panel-actions';
 -import { LINK_PANEL_ID } from 'store/link-panel/link-panel-actions';
 -import { LinkMiddlewareService } from 'store/link-panel/link-panel-middleware-service';
 -import { API_CLIENT_AUTHORIZATION_PANEL_ID } from 'store/api-client-authorizations/api-client-authorizations-actions';
 -import { ApiClientAuthorizationMiddlewareService } from 'store/api-client-authorizations/api-client-authorizations-middleware-service';
 -import { PublicFavoritesMiddlewareService } from 'store/public-favorites-panel/public-favorites-middleware-service';
 -import { PUBLIC_FAVORITE_PANEL_ID } from 'store/public-favorites-panel/public-favorites-action';
 -import { publicFavoritesReducer } from 'store/public-favorites/public-favorites-reducer';
 -import { linkAccountPanelReducer } from './link-account-panel/link-account-panel-reducer';
 -import { CollectionsWithSameContentAddressMiddlewareService } from 'store/collections-content-address-panel/collections-content-address-middleware-service';
 -import { COLLECTIONS_CONTENT_ADDRESS_PANEL_ID } from 'store/collections-content-address-panel/collections-content-address-panel-actions';
 -import { ownerNameReducer } from 'store/owner-name/owner-name-reducer';
 -import { SubprocessMiddlewareService } from 'store/subprocess-panel/subprocess-panel-middleware-service';
 -import { SUBPROCESS_PANEL_ID } from 'store/subprocess-panel/subprocess-panel-actions';
 -import { ALL_PROCESSES_PANEL_ID } from './all-processes-panel/all-processes-panel-action';
 -import { Config } from 'common/config';
 -import { pluginConfig } from 'plugins';
 -import { MiddlewareListReducer } from 'common/plugintypes';
 -import { tooltipsMiddleware } from './tooltips/tooltips-middleware';
 -import { sidePanelReducer } from './side-panel/side-panel-reducer'
 -import { bannerReducer } from './banner/banner-reducer';
 -import { composeWithDevTools } from 'redux-devtools-extension';
++import { repositoriesReducer } from "store/repositories/repositories-reducer";
++import { keepServicesReducer } from "store/keep-services/keep-services-reducer";
++import { UserMiddlewareService } from "store/users/user-panel-middleware-service";
++import { USERS_PANEL_ID } from "store/users/users-actions";
++import { UserProfileGroupsMiddlewareService } from "store/user-profile/user-profile-groups-middleware-service";
++import { USER_PROFILE_PANEL_ID } from "store/user-profile/user-profile-actions";
++import { GroupsPanelMiddlewareService } from "store/groups-panel/groups-panel-middleware-service";
++import { GROUPS_PANEL_ID } from "store/groups-panel/groups-panel-actions";
++import { GroupDetailsPanelMembersMiddlewareService } from "store/group-details-panel/group-details-panel-members-middleware-service";
++import { GroupDetailsPanelPermissionsMiddlewareService } from "store/group-details-panel/group-details-panel-permissions-middleware-service";
++import { GROUP_DETAILS_MEMBERS_PANEL_ID, GROUP_DETAILS_PERMISSIONS_PANEL_ID } from "store/group-details-panel/group-details-panel-actions";
++import { LINK_PANEL_ID } from "store/link-panel/link-panel-actions";
++import { LinkMiddlewareService } from "store/link-panel/link-panel-middleware-service";
++import { API_CLIENT_AUTHORIZATION_PANEL_ID } from "store/api-client-authorizations/api-client-authorizations-actions";
++import { ApiClientAuthorizationMiddlewareService } from "store/api-client-authorizations/api-client-authorizations-middleware-service";
++import { PublicFavoritesMiddlewareService } from "store/public-favorites-panel/public-favorites-middleware-service";
++import { PUBLIC_FAVORITE_PANEL_ID } from "store/public-favorites-panel/public-favorites-action";
++import { publicFavoritesReducer } from "store/public-favorites/public-favorites-reducer";
++import { linkAccountPanelReducer } from "./link-account-panel/link-account-panel-reducer";
++import { CollectionsWithSameContentAddressMiddlewareService } from "store/collections-content-address-panel/collections-content-address-middleware-service";
++import { COLLECTIONS_CONTENT_ADDRESS_PANEL_ID } from "store/collections-content-address-panel/collections-content-address-panel-actions";
++import { ownerNameReducer } from "store/owner-name/owner-name-reducer";
++import { SubprocessMiddlewareService } from "store/subprocess-panel/subprocess-panel-middleware-service";
++import { SUBPROCESS_PANEL_ID } from "store/subprocess-panel/subprocess-panel-actions";
++import { ALL_PROCESSES_PANEL_ID } from "./all-processes-panel/all-processes-panel-action";
++import { Config } from "common/config";
++import { pluginConfig } from "plugins";
++import { MiddlewareListReducer } from "common/plugintypes";
++import { tooltipsMiddleware } from "./tooltips/tooltips-middleware";
++import { sidePanelReducer } from "./side-panel/side-panel-reducer";
++import { bannerReducer } from "./banner/banner-reducer";
++import { multiselectReducer } from "./multiselect/multiselect-reducer";
 +
 +declare global {
 +    interface Window {
 +        __REDUX_DEVTOOLS_EXTENSION_COMPOSE__?: typeof compose;
 +    }
 +}
 +
- const composeEnhancers = (process.env.NODE_ENV === 'development' && window && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose;
++const composeEnhancers = (process.env.NODE_ENV === "development" && window && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose;
++import { composeWithDevTools } from "redux-devtools-extension";
  
  export type RootState = ReturnType<ReturnType<typeof createRootReducer>>;
  
@@@ -94,26 -86,57 +95,32 @@@ export type RootStore = Store<RootState
  export function configureStore(history: History, services: ServiceRepository, config: Config): RootStore {
      const rootReducer = createRootReducer(services);
  
 -    const projectPanelMiddleware = dataExplorerMiddleware(
 -        new ProjectPanelMiddlewareService(services, PROJECT_PANEL_ID)
 -    );
 -    const favoritePanelMiddleware = dataExplorerMiddleware(
 -        new FavoritePanelMiddlewareService(services, FAVORITE_PANEL_ID)
 -    );
 -    const allProcessessPanelMiddleware = dataExplorerMiddleware(
 -        new AllProcessesPanelMiddlewareService(services, ALL_PROCESSES_PANEL_ID)
 -    );
 -    const trashPanelMiddleware = dataExplorerMiddleware(
 -        new TrashPanelMiddlewareService(services, TRASH_PANEL_ID)
 -    );
 -    const searchResultsPanelMiddleware = dataExplorerMiddleware(
 -        new SearchResultsMiddlewareService(services, SEARCH_RESULTS_PANEL_ID)
 -    );
 -    const sharedWithMePanelMiddleware = dataExplorerMiddleware(
 -        new SharedWithMeMiddlewareService(services, SHARED_WITH_ME_PANEL_ID)
 -    );
 -    const workflowPanelMiddleware = dataExplorerMiddleware(
 -        new WorkflowMiddlewareService(services, WORKFLOW_PANEL_ID)
 -    );
 -    const userPanelMiddleware = dataExplorerMiddleware(
 -        new UserMiddlewareService(services, USERS_PANEL_ID)
 -    );
 -    const userProfileGroupsMiddleware = dataExplorerMiddleware(
 -        new UserProfileGroupsMiddlewareService(services, USER_PROFILE_PANEL_ID)
 -    );
 -    const groupsPanelMiddleware = dataExplorerMiddleware(
 -        new GroupsPanelMiddlewareService(services, GROUPS_PANEL_ID)
 -    );
 +    const projectPanelMiddleware = dataExplorerMiddleware(new ProjectPanelMiddlewareService(services, PROJECT_PANEL_ID));
 +    const favoritePanelMiddleware = dataExplorerMiddleware(new FavoritePanelMiddlewareService(services, FAVORITE_PANEL_ID));
 +    const allProcessessPanelMiddleware = dataExplorerMiddleware(new AllProcessesPanelMiddlewareService(services, ALL_PROCESSES_PANEL_ID));
 +    const trashPanelMiddleware = dataExplorerMiddleware(new TrashPanelMiddlewareService(services, TRASH_PANEL_ID));
 +    const searchResultsPanelMiddleware = dataExplorerMiddleware(new SearchResultsMiddlewareService(services, SEARCH_RESULTS_PANEL_ID));
 +    const sharedWithMePanelMiddleware = dataExplorerMiddleware(new SharedWithMeMiddlewareService(services, SHARED_WITH_ME_PANEL_ID));
 +    const workflowPanelMiddleware = dataExplorerMiddleware(new WorkflowMiddlewareService(services, WORKFLOW_PANEL_ID));
 +    const userPanelMiddleware = dataExplorerMiddleware(new UserMiddlewareService(services, USERS_PANEL_ID));
 +    const userProfileGroupsMiddleware = dataExplorerMiddleware(new UserProfileGroupsMiddlewareService(services, USER_PROFILE_PANEL_ID));
 +    const groupsPanelMiddleware = dataExplorerMiddleware(new GroupsPanelMiddlewareService(services, GROUPS_PANEL_ID));
-     const groupDetailsPanelMembersMiddleware = dataExplorerMiddleware(new GroupDetailsPanelMembersMiddlewareService(services, GROUP_DETAILS_MEMBERS_PANEL_ID));
+     const groupDetailsPanelMembersMiddleware = dataExplorerMiddleware(
+         new GroupDetailsPanelMembersMiddlewareService(services, GROUP_DETAILS_MEMBERS_PANEL_ID)
+     );
      const groupDetailsPanelPermissionsMiddleware = dataExplorerMiddleware(
          new GroupDetailsPanelPermissionsMiddlewareService(services, GROUP_DETAILS_PERMISSIONS_PANEL_ID)
      );
 -    const linkPanelMiddleware = dataExplorerMiddleware(
 -        new LinkMiddlewareService(services, LINK_PANEL_ID)
 -    );
 +    const linkPanelMiddleware = dataExplorerMiddleware(new LinkMiddlewareService(services, LINK_PANEL_ID));
-     const apiClientAuthorizationMiddlewareService = dataExplorerMiddleware(new ApiClientAuthorizationMiddlewareService(services, API_CLIENT_AUTHORIZATION_PANEL_ID));
+     const apiClientAuthorizationMiddlewareService = dataExplorerMiddleware(
+         new ApiClientAuthorizationMiddlewareService(services, API_CLIENT_AUTHORIZATION_PANEL_ID)
+     );
 -    const publicFavoritesMiddleware = dataExplorerMiddleware(
 -        new PublicFavoritesMiddlewareService(services, PUBLIC_FAVORITE_PANEL_ID)
 -    );
 +    const publicFavoritesMiddleware = dataExplorerMiddleware(new PublicFavoritesMiddlewareService(services, PUBLIC_FAVORITE_PANEL_ID));
-     const collectionsContentAddress = dataExplorerMiddleware(new CollectionsWithSameContentAddressMiddlewareService(services, COLLECTIONS_CONTENT_ADDRESS_PANEL_ID));
+     const collectionsContentAddress = dataExplorerMiddleware(
+         new CollectionsWithSameContentAddressMiddlewareService(services, COLLECTIONS_CONTENT_ADDRESS_PANEL_ID)
+     );
 -    const subprocessMiddleware = dataExplorerMiddleware(
 -        new SubprocessMiddlewareService(services, SUBPROCESS_PANEL_ID)
 -    );
 +    const subprocessMiddleware = dataExplorerMiddleware(new SubprocessMiddlewareService(services, SUBPROCESS_PANEL_ID));
 +
      const redirectToMiddleware = (store: any) => (next: any) => (action: any) => {
          const state = store.getState();
  
@@@ -153,7 -177,7 +160,9 @@@
  
      middlewares = pluginConfig.middlewares.reduce(reduceMiddlewaresFn, middlewares);
  
-     const enhancer = composeEnhancers(applyMiddleware(redirectToMiddleware, ...middlewares));
 -    const enhancer = composeWithDevTools({/* options */ })(applyMiddleware(redirectToMiddleware, ...middlewares));
++    const enhancer = composeWithDevTools({
++        /* options */
++    })(applyMiddleware(redirectToMiddleware, ...middlewares));
      return createStore(rootReducer, enhancer);
  }
  
diff --cc src/store/workbench/workbench-actions.ts
index 4e27226d,a3c3a096..c6f1d1b1
--- a/src/store/workbench/workbench-actions.ts
+++ b/src/store/workbench/workbench-actions.ts
@@@ -2,18 -2,20 +2,13 @@@
  //
  // SPDX-License-Identifier: AGPL-3.0
  
 -import { Dispatch } from 'redux';
 -import { RootState } from 'store/store';
 -import { getUserUuid } from 'common/getuser';
 -import { loadDetailsPanel } from 'store/details-panel/details-panel-action';
 -import { snackbarActions, SnackbarKind } from 'store/snackbar/snackbar-actions';
 -import {
 -    favoritePanelActions,
 -    loadFavoritePanel,
 -} from 'store/favorite-panel/favorite-panel-action';
 -import {
 -    getProjectPanelCurrentUuid,
 -    projectPanelActions,
 -    setIsProjectPanelTrashed,
 -} from 'store/project-panel/project-panel-action';
 +import { Dispatch } from "redux";
 +import { RootState } from "store/store";
 +import { getUserUuid } from "common/getuser";
 +import { loadDetailsPanel } from "store/details-panel/details-panel-action";
 +import { snackbarActions, SnackbarKind } from "store/snackbar/snackbar-actions";
 +import { favoritePanelActions, loadFavoritePanel } from "store/favorite-panel/favorite-panel-action";
- import {
-     getProjectPanelCurrentUuid,
-     openProjectPanel,
-     projectPanelActions,
-     setIsProjectPanelTrashed,
- } from "store/project-panel/project-panel-action";
++import { getProjectPanelCurrentUuid, projectPanelActions, setIsProjectPanelTrashed } from "store/project-panel/project-panel-action";
  import {
      activateSidePanelTreeItem,
      initSidePanelTree,
@@@ -784,22 -861,32 +779,21 @@@ export const loadGroupDetailsPanel = (g
          dispatch(groupDetailsPanelActions.loadGroupDetailsPanel(groupUuid));
      });
  
 -const finishLoadingProject =
 -    (project: GroupContentsResource | string) =>
 -        async (dispatch: Dispatch<any>) => {
 -            const uuid = typeof project === 'string' ? project : project.uuid;
 -            dispatch(loadDetailsPanel(uuid));
 -            if (typeof project !== 'string') {
 -                dispatch(updateResources([project]));
 -            }
 -        };
 -
 -const loadGroupContentsResource = async (params: {
 -    uuid: string;
 -    userUuid: string;
 -    services: ServiceRepository;
 -}) => {
 -    const filters = new FilterBuilder()
 -        .addEqual('uuid', params.uuid)
 -        .getFilters();
 -    const { items } = await params.services.groupsService.contents(
 -        params.userUuid,
 -        {
 -            filters,
 -            recursive: true,
 -            includeTrash: true,
 -        }
 -    );
 +const finishLoadingProject = (project: GroupContentsResource | string) => async (dispatch: Dispatch<any>) => {
 +    const uuid = typeof project === "string" ? project : project.uuid;
-     dispatch(openProjectPanel(uuid));
 +    dispatch(loadDetailsPanel(uuid));
 +    if (typeof project !== "string") {
 +        dispatch(updateResources([project]));
 +    }
 +};
 +
 +const loadGroupContentsResource = async (params: { uuid: string; userUuid: string; services: ServiceRepository }) => {
 +    const filters = new FilterBuilder().addEqual("uuid", params.uuid).getFilters();
 +    const { items } = await params.services.groupsService.contents(params.userUuid, {
 +        filters,
 +        recursive: true,
 +        includeTrash: true,
 +    });
      const resource = items.shift();
      let handler: GroupContentsHandler;
      if (resource) {
diff --cc src/views-components/context-menu/action-sets/collection-files-item-action-set.ts
index 03674665,d0758d70..fb158a82
--- a/src/views-components/context-menu/action-sets/collection-files-item-action-set.ts
+++ b/src/views-components/context-menu/action-sets/collection-files-item-action-set.ts
@@@ -2,68 -2,81 +2,102 @@@
  //
  // SPDX-License-Identifier: AGPL-3.0
  
- import { ContextMenuActionSet } from '../context-menu-action-set';
- import { RemoveIcon, RenameIcon } from 'components/icon/icon';
- import { DownloadCollectionFileAction } from '../actions/download-collection-file-action';
- import { openFileRemoveDialog, openRenameFileDialog } from 'store/collection-panel/collection-panel-files/collection-panel-files-actions';
- import { CollectionFileViewerAction } from 'views-components/context-menu/actions/collection-file-viewer-action';
- import { CollectionCopyToClipboardAction } from '../actions/collection-copy-to-clipboard-action';
+ import { ContextMenuActionSet } from "../context-menu-action-set";
+ import { FileCopyIcon, FileMoveIcon, RemoveIcon, RenameIcon } from "components/icon/icon";
+ import { DownloadCollectionFileAction } from "../actions/download-collection-file-action";
 -import { openFileRemoveDialog, openRenameFileDialog } from 'store/collection-panel/collection-panel-files/collection-panel-files-actions';
 -import { CollectionFileViewerAction } from 'views-components/context-menu/actions/collection-file-viewer-action';
++import { openFileRemoveDialog, openRenameFileDialog } from "store/collection-panel/collection-panel-files/collection-panel-files-actions";
++import { CollectionFileViewerAction } from "views-components/context-menu/actions/collection-file-viewer-action";
+ import { CollectionCopyToClipboardAction } from "../actions/collection-copy-to-clipboard-action";
 -import { openCollectionPartialMoveToExistingCollectionDialog, openCollectionPartialMoveToNewCollectionDialog } from "store/collections/collection-partial-move-actions";
 -import { openCollectionPartialCopyToExistingCollectionDialog, openCollectionPartialCopyToNewCollectionDialog } from "store/collections/collection-partial-copy-actions";
++import {
++    openCollectionPartialMoveToExistingCollectionDialog,
++    openCollectionPartialMoveToNewCollectionDialog,
++} from "store/collections/collection-partial-move-actions";
++import {
++    openCollectionPartialCopyToExistingCollectionDialog,
++    openCollectionPartialCopyToNewCollectionDialog,
++} from "store/collections/collection-partial-copy-actions";
  
 -export const readOnlyCollectionDirectoryItemActionSet: ContextMenuActionSet = [[
 -    {
 -        name: "Copy item into new collection",
 -        icon: FileCopyIcon,
 -        execute: (dispatch, resource) => {
 -            dispatch<any>(openCollectionPartialCopyToNewCollectionDialog(resource));
 -        }
 -    },
 -    {
 -        name: "Copy item into existing collection",
 -        icon: FileCopyIcon,
 -        execute: (dispatch, resource) => {
 -            dispatch<any>(openCollectionPartialCopyToExistingCollectionDialog(resource));
 -        }
 -    },
 -    {
 -        component: CollectionFileViewerAction,
 -        execute: () => { return; },
 -    },
 -    {
 -        component: CollectionCopyToClipboardAction,
 -        execute: () => { return; },
 -    }
 -]];
 +export const readOnlyCollectionDirectoryItemActionSet: ContextMenuActionSet = [
 +    [
++        {
++            name: "Copy item into new collection",
++            icon: FileCopyIcon,
++            execute: (dispatch, resources) => {
++                dispatch<any>(openCollectionPartialCopyToNewCollectionDialog(resources[0]));
++            },
++        },
++        {
++            name: "Copy item into existing collection",
++            icon: FileCopyIcon,
++            execute: (dispatch, resources) => {
++                dispatch<any>(openCollectionPartialCopyToExistingCollectionDialog(resources[0]));
++            },
++        },
 +        {
 +            component: CollectionFileViewerAction,
 +            execute: () => {
 +                return;
 +            },
 +        },
 +        {
 +            component: CollectionCopyToClipboardAction,
 +            execute: () => {
 +                return;
 +            },
 +        },
 +    ],
 +];
  
 -export const readOnlyCollectionFileItemActionSet: ContextMenuActionSet = [[
 -    {
 -        component: DownloadCollectionFileAction,
 -        execute: () => { return; }
 -    },
 -    ...readOnlyCollectionDirectoryItemActionSet.reduce((prev, next) => prev.concat(next), []),
 -]];
 +export const readOnlyCollectionFileItemActionSet: ContextMenuActionSet = [
 +    [
 +        {
 +            component: DownloadCollectionFileAction,
 +            execute: () => {
 +                return;
 +            },
 +        },
 +        ...readOnlyCollectionDirectoryItemActionSet.reduce((prev, next) => prev.concat(next), []),
 +    ],
 +];
  
 -const writableActionSet: ContextMenuActionSet = [[
 -    {
 -        name: "Move item into new collection",
 -        icon: FileMoveIcon,
 -        execute: (dispatch, resource) => {
 -            dispatch<any>(openCollectionPartialMoveToNewCollectionDialog(resource));
 -        }
 -    },
 -    {
 -        name: "Move item into existing collection",
 -        icon: FileMoveIcon,
 -        execute: (dispatch, resource) => {
 -            dispatch<any>(openCollectionPartialMoveToExistingCollectionDialog(resource));
 -        }
 -    },
 -    {
 -        name: "Rename",
 -        icon: RenameIcon,
 -        execute: (dispatch, resource) => {
 -            dispatch<any>(openRenameFileDialog({
 -                name: resource.name,
 -                id: resource.uuid,
 -                path: resource.uuid.split('/').slice(1).join('/') }));
 -        }
 -    },
 -    {
 -        name: "Remove",
 -        icon: RemoveIcon,
 -        execute: (dispatch, resource) => {
 -            dispatch<any>(openFileRemoveDialog(resource.uuid));
 -        }
 -    }
 -]];
 +const writableActionSet: ContextMenuActionSet = [
 +    [
 +        {
-             name: 'Rename',
++            name: "Move item into new collection",
++            icon: FileMoveIcon,
++            execute: (dispatch, resources) => {
++                dispatch<any>(openCollectionPartialMoveToNewCollectionDialog(resources[0]));
++            },
++        },
++        {
++            name: "Move item into existing collection",
++            icon: FileMoveIcon,
++            execute: (dispatch, resources) => {
++                dispatch<any>(openCollectionPartialMoveToExistingCollectionDialog(resources[0]));
++            },
++        },
++        {
++            name: "Rename",
 +            icon: RenameIcon,
 +            execute: (dispatch, resources) => {
-                 resources.forEach((resource) =>
-                     dispatch<any>(
-                         openRenameFileDialog({
-                             name: resource.name,
-                             id: resource.uuid,
-                             path: resource.uuid.split('/').slice(1).join('/'),
-                         })
-                     )
++                dispatch<any>(
++                    openRenameFileDialog({
++                        name: resources[0].name,
++                        id: resources[0].uuid,
++                        path: resources[0].uuid.split("/").slice(1).join("/"),
++                    })
 +                );
 +            },
 +        },
 +        {
-             name: 'Remove',
++            name: "Remove",
 +            icon: RemoveIcon,
 +            execute: (dispatch, resources) => {
-                 resources.forEach((resource) => dispatch<any>(openFileRemoveDialog(resource.uuid)));
++                dispatch<any>(openFileRemoveDialog(resources[0].uuid));
 +            },
 +        },
 +    ],
 +];
  
  export const collectionDirectoryItemActionSet: ContextMenuActionSet = readOnlyCollectionDirectoryItemActionSet.concat(writableActionSet);
  
diff --cc src/views-components/context-menu/action-sets/workflow-action-set.ts
index cfcb2c24,1baf0422..4a1460bf
--- a/src/views-components/context-menu/action-sets/workflow-action-set.ts
+++ b/src/views-components/context-menu/action-sets/workflow-action-set.ts
@@@ -2,49 -2,65 +2,62 @@@
  //
  // SPDX-License-Identifier: AGPL-3.0
  
- import { ContextMenuActionSet } from 'views-components/context-menu/context-menu-action-set';
- import { openRunProcess } from 'store/workflow-panel/workflow-panel-actions';
- import { DetailsIcon, AdvancedIcon, OpenIcon, Link, StartIcon } from 'components/icon/icon';
- import { copyToClipboardAction, openInNewTabAction } from 'store/open-in-new-tab/open-in-new-tab.actions';
- import { toggleDetailsPanel } from 'store/details-panel/details-panel-action';
- import { openAdvancedTabDialog } from 'store/advanced-tab/advanced-tab';
+ import { ContextMenuActionSet } from "views-components/context-menu/context-menu-action-set";
+ import { openRunProcess, deleteWorkflow } from "store/workflow-panel/workflow-panel-actions";
 -import {
 -    DetailsIcon,
 -    AdvancedIcon,
 -    OpenIcon,
 -    Link,
 -    StartIcon,
 -    TrashIcon
 -} from "components/icon/icon";
++import { DetailsIcon, AdvancedIcon, OpenIcon, Link, StartIcon, TrashIcon } from "components/icon/icon";
+ import { copyToClipboardAction, openInNewTabAction } from "store/open-in-new-tab/open-in-new-tab.actions";
 -import { toggleDetailsPanel } from 'store/details-panel/details-panel-action';
++import { toggleDetailsPanel } from "store/details-panel/details-panel-action";
+ import { openAdvancedTabDialog } from "store/advanced-tab/advanced-tab";
  
- export const workflowActionSet: ContextMenuActionSet = [
 -export const readOnlyWorkflowActionSet: ContextMenuActionSet = [[
 -    {
 -        icon: OpenIcon,
 -        name: "Open in new tab",
 -        execute: (dispatch, resource) => {
 -            dispatch<any>(openInNewTabAction(resource));
 -        }
 -    },
 -    {
 -        icon: Link,
 -        name: "Copy to clipboard",
 -        execute: (dispatch, resource) => {
 -            dispatch<any>(copyToClipboardAction(resource));
 -        }
 -    },
 -    {
 -        icon: DetailsIcon,
 -        name: "View details",
 -        execute: dispatch => {
 -            dispatch<any>(toggleDetailsPanel());
 -        }
 -    },
 -    {
 -        icon: AdvancedIcon,
 -        name: "API Details",
 -        execute: (dispatch, resource) => {
 -            dispatch<any>(openAdvancedTabDialog(resource.uuid));
 -        }
 -    },
 -    {
 -        icon: StartIcon,
 -        name: "Run Workflow",
 -        execute: (dispatch, resource) => {
 -            dispatch<any>(openRunProcess(resource.uuid, resource.ownerUuid, resource.name));
 -        }
 -    }
 -]];
++export const readOnlyWorkflowActionSet: ContextMenuActionSet = [
 +    [
 +        {
 +            icon: OpenIcon,
-             name: 'Open in new tab',
++            name: "Open in new tab",
 +            execute: (dispatch, resources) => {
-                 resources.forEach((resource) => dispatch<any>(openInNewTabAction(resource)));
++                dispatch<any>(openInNewTabAction(resources[0]));
 +            },
 +        },
 +        {
 +            icon: Link,
-             name: 'Copy to clipboard',
++            name: "Copy to clipboard",
 +            execute: (dispatch, resources) => {
 +                dispatch<any>(copyToClipboardAction(resources));
 +            },
 +        },
 +        {
 +            icon: DetailsIcon,
-             name: 'View details',
-             execute: (dispatch) => {
++            name: "View details",
++            execute: dispatch => {
 +                dispatch<any>(toggleDetailsPanel());
 +            },
 +        },
 +        {
 +            icon: AdvancedIcon,
-             name: 'API Details',
++            name: "API Details",
 +            execute: (dispatch, resources) => {
-                 resources.forEach((resource) => dispatch<any>(openAdvancedTabDialog(resource.uuid)));
++                dispatch<any>(openAdvancedTabDialog(resources[0].uuid));
 +            },
 +        },
 +        {
 +            icon: StartIcon,
-             name: 'Run Workflow',
++            name: "Run Workflow",
++            execute: (dispatch, resources) => {
++                dispatch<any>(openRunProcess(resources[0].uuid, resources[0].ownerUuid, resources[0].name));
++            },
++        },
++    ],
++];
+ 
 -export const workflowActionSet: ContextMenuActionSet = [[
 -    ...readOnlyWorkflowActionSet[0],
 -    {
 -        icon: TrashIcon,
 -        name: "Delete Workflow",
 -        execute: (dispatch, resource) => {
 -            dispatch<any>(deleteWorkflow(resource.uuid, resource.ownerUuid));
 -        }
 -    },
 -]];
++export const workflowActionSet: ContextMenuActionSet = [
++    [
++        ...readOnlyWorkflowActionSet[0],
++        {
++            icon: TrashIcon,
++            name: "Delete Workflow",
 +            execute: (dispatch, resources) => {
-                 resources.forEach((resource) => dispatch<any>(openRunProcess(resource.uuid, resource.ownerUuid, resource.name)));
++                dispatch<any>(deleteWorkflow(resources[0].uuid, resources[0].ownerUuid));
 +            },
 +        },
 +    ],
 +];
diff --cc src/views-components/context-menu/context-menu.tsx
index e7bfff9e,1b4610ef..8cb3394a
--- a/src/views-components/context-menu/context-menu.tsx
+++ b/src/views-components/context-menu/context-menu.tsx
@@@ -94,16 -95,15 +97,15 @@@ export enum ContextMenuKind 
      COLLECTION_DIRECTORY_ITEM = "CollectionDirectoryItem",
      READONLY_COLLECTION_FILE_ITEM = "ReadOnlyCollectionFileItem",
      READONLY_COLLECTION_DIRECTORY_ITEM = "ReadOnlyCollectionDirectoryItem",
-     COLLECTION_FILES_NOT_SELECTED = "CollectionFilesNotSelected",
 -    COLLECTION = 'Collection',
 -    COLLECTION_ADMIN = 'CollectionAdmin',
 -    READONLY_COLLECTION = 'ReadOnlyCollection',
 -    OLD_VERSION_COLLECTION = 'OldVersionCollection',
 -    TRASHED_COLLECTION = 'TrashedCollection',
 +    COLLECTION = "Collection",
 +    COLLECTION_ADMIN = "CollectionAdmin",
 +    READONLY_COLLECTION = "ReadOnlyCollection",
 +    OLD_VERSION_COLLECTION = "OldVersionCollection",
 +    TRASHED_COLLECTION = "TrashedCollection",
      PROCESS = "Process",
 -    PROCESS_ADMIN = 'ProcessAdmin',
 -    PROCESS_RESOURCE = 'ProcessResource',
 -    READONLY_PROCESS_RESOURCE = 'ReadOnlyProcessResource',
 +    PROCESS_ADMIN = "ProcessAdmin",
 +    PROCESS_RESOURCE = "ProcessResource",
 +    READONLY_PROCESS_RESOURCE = "ReadOnlyProcessResource",
      PROCESS_LOGS = "ProcessLogs",
      REPOSITORY = "Repository",
      SSH_KEY = "SshKey",
@@@ -115,5 -115,6 +117,6 @@@
      PERMISSION_EDIT = "PermissionEdit",
      LINK = "Link",
      WORKFLOW = "Workflow",
+     READONLY_WORKFLOW = "ReadOnlyWorkflow",
 -    SEARCH_RESULTS = "SearchResults"
 +    SEARCH_RESULTS = "SearchResults",
  }
diff --cc src/views-components/data-explorer/renderers.tsx
index b6623bb6,dca06084..5accd052
--- a/src/views-components/data-explorer/renderers.tsx
+++ b/src/views-components/data-explorer/renderers.tsx
@@@ -2,10 -2,10 +2,17 @@@
  //
  // SPDX-License-Identifier: AGPL-3.0
  
++<<<<<<< HEAD
 +import React from 'react';
 +import { Grid, Typography, withStyles, Tooltip, IconButton, Checkbox, Chip } from '@material-ui/core';
 +import { FavoriteStar, PublicFavoriteStar } from '../favorite-star/favorite-star';
 +import { Resource, ResourceKind, TrashableResource } from 'models/resource';
++=======
+ import React from "react";
+ import { Grid, Typography, withStyles, Tooltip, IconButton, Checkbox, Chip } from "@material-ui/core";
+ import { FavoriteStar, PublicFavoriteStar } from "../favorite-star/favorite-star";
+ import { Resource, ResourceKind, TrashableResource } from "models/resource";
++>>>>>>> main
  import {
      FreezeIcon,
      ProjectIcon,
@@@ -21,53 -21,61 +28,109 @@@
      ActiveIcon,
      SetupIcon,
      InactiveIcon,
++<<<<<<< HEAD
 +} from 'components/icon/icon';
 +import { formatDate, formatFileSize, formatTime } from 'common/formatters';
 +import { resourceLabel } from 'common/labels';
 +import { connect, DispatchProp } from 'react-redux';
 +import { RootState } from 'store/store';
 +import { getResource, filterResources } from 'store/resources/resources';
 +import { GroupContentsResource } from 'services/groups-service/groups-service';
 +import { getProcess, Process, getProcessStatus, getProcessStatusStyles, getProcessRuntime } from 'store/processes/process';
 +import { ArvadosTheme } from 'common/custom-theme';
 +import { compose, Dispatch } from 'redux';
 +import { WorkflowResource } from 'models/workflow';
 +import { ResourceStatus as WorkflowStatus } from 'views/workflow-panel/workflow-panel-view';
 +import { getUuidPrefix, openRunProcess } from 'store/workflow-panel/workflow-panel-actions';
 +import { openSharingDialog } from 'store/sharing-dialog/sharing-dialog-actions';
 +import { getUserFullname, getUserDisplayName, User, UserResource } from 'models/user';
 +import { toggleIsAdmin } from 'store/users/users-actions';
 +import { LinkClass, LinkResource } from 'models/link';
 +import { navigateTo, navigateToGroupDetails, navigateToUserProfile } from 'store/navigation/navigation-action';
 +import { withResourceData } from 'views-components/data-explorer/with-resources';
 +import { CollectionResource } from 'models/collection';
 +import { IllegalNamingWarning } from 'components/warning/warning';
 +import { loadResource } from 'store/resources/resources-actions';
 +import { BuiltinGroups, getBuiltinGroupUuid, GroupClass, GroupResource, isBuiltinGroup } from 'models/group';
 +import { openRemoveGroupMemberDialog } from 'store/group-details-panel/group-details-panel-actions';
 +import { setMemberIsHidden } from 'store/group-details-panel/group-details-panel-actions';
 +import { formatPermissionLevel } from 'views-components/sharing-dialog/permission-select';
 +import { PermissionLevel } from 'models/permission';
 +import { openPermissionEditContextMenu } from 'store/context-menu/context-menu-actions';
 +import { getUserUuid } from 'common/getuser';
 +import { VirtualMachinesResource } from 'models/virtual-machines';
 +import { CopyToClipboardSnackbar } from 'components/copy-to-clipboard-snackbar/copy-to-clipboard-snackbar';
 +import { ProjectResource } from 'models/project';
 +import { ProcessResource } from 'models/process';
 +
 +const renderName = (dispatch: Dispatch, item: GroupContentsResource) => {
 +    const navFunc = 'groupClass' in item && item.groupClass === GroupClass.ROLE ? navigateToGroupDetails : navigateTo;
 +    return (
 +        <Grid container alignItems='center' wrap='nowrap' spacing={16}>
 +            <Grid item>{renderIcon(item)}</Grid>
 +            <Grid item>
 +                <Typography color='primary' style={{ width: 'auto', cursor: 'pointer' }} onClick={() => dispatch<any>(navFunc(item.uuid))}>
++=======
+ } from "components/icon/icon";
+ import { formatDate, formatFileSize, formatTime } from "common/formatters";
+ import { resourceLabel } from "common/labels";
+ import { connect, DispatchProp } from "react-redux";
+ import { RootState } from "store/store";
+ import { getResource, filterResources } from "store/resources/resources";
+ import { GroupContentsResource } from "services/groups-service/groups-service";
+ import { getProcess, Process, getProcessStatus, getProcessStatusStyles, getProcessRuntime } from "store/processes/process";
+ import { ArvadosTheme } from "common/custom-theme";
+ import { compose, Dispatch } from "redux";
+ import { WorkflowResource } from "models/workflow";
+ import { ResourceStatus as WorkflowStatus } from "views/workflow-panel/workflow-panel-view";
+ import { getUuidPrefix, openRunProcess } from "store/workflow-panel/workflow-panel-actions";
+ import { openSharingDialog } from "store/sharing-dialog/sharing-dialog-actions";
+ import { getUserFullname, getUserDisplayName, User, UserResource } from "models/user";
+ import { toggleIsAdmin } from "store/users/users-actions";
+ import { LinkClass, LinkResource } from "models/link";
+ import { navigateTo, navigateToGroupDetails, navigateToUserProfile } from "store/navigation/navigation-action";
+ import { withResourceData } from "views-components/data-explorer/with-resources";
+ import { CollectionResource } from "models/collection";
+ import { IllegalNamingWarning } from "components/warning/warning";
+ import { loadResource } from "store/resources/resources-actions";
+ import { BuiltinGroups, getBuiltinGroupUuid, GroupClass, GroupResource, isBuiltinGroup } from "models/group";
+ import { openRemoveGroupMemberDialog } from "store/group-details-panel/group-details-panel-actions";
+ import { setMemberIsHidden } from "store/group-details-panel/group-details-panel-actions";
+ import { formatPermissionLevel } from "views-components/sharing-dialog/permission-select";
+ import { PermissionLevel } from "models/permission";
+ import { openPermissionEditContextMenu } from "store/context-menu/context-menu-actions";
+ import { VirtualMachinesResource } from "models/virtual-machines";
+ import { CopyToClipboardSnackbar } from "components/copy-to-clipboard-snackbar/copy-to-clipboard-snackbar";
+ import { ProjectResource } from "models/project";
+ import { ProcessResource } from "models/process";
+ 
+ const renderName = (dispatch: Dispatch, item: GroupContentsResource) => {
+     const navFunc = "groupClass" in item && item.groupClass === GroupClass.ROLE ? navigateToGroupDetails : navigateTo;
+     return (
+         <Grid
+             container
+             alignItems="center"
+             wrap="nowrap"
+             spacing={16}
+         >
+             <Grid item>{renderIcon(item)}</Grid>
+             <Grid item>
+                 <Typography
+                     color="primary"
+                     style={{ width: "auto", cursor: "pointer" }}
+                     onClick={() => dispatch<any>(navFunc(item.uuid))}
+                 >
++>>>>>>> main
                      {item.kind === ResourceKind.PROJECT || item.kind === ResourceKind.COLLECTION ? <IllegalNamingWarning name={item.name} /> : null}
                      {item.name}
                  </Typography>
              </Grid>
              <Grid item>
++<<<<<<< HEAD
 +                <Typography variant='caption'>
++=======
+                 <Typography variant="caption">
++>>>>>>> main
                      <FavoriteStar resourceUuid={item.uuid} />
                      <PublicFavoriteStar resourceUuid={item.uuid} />
                      {item.kind === ResourceKind.PROJECT && <FrozenProject item={item} />}
@@@ -87,8 -95,12 +150,17 @@@ const FrozenProject = (props: { item: P
  
      if (props.item.frozenByUuid) {
          return (
++<<<<<<< HEAD
 +            <Tooltip onOpen={getFullName} enterDelay={500} title={<span>Project was frozen by {fullUsername}</span>}>
 +                <FreezeIcon style={{ fontSize: 'inherit' }} />
++=======
+             <Tooltip
+                 onOpen={getFullName}
+                 enterDelay={500}
+                 title={<span>Project was frozen by {fullUsername}</span>}
+             >
+                 <FreezeIcon style={{ fontSize: "inherit" }} />
++>>>>>>> main
              </Tooltip>
          );
      } else {
@@@ -124,17 -136,28 +196,39 @@@ const renderIcon = (item: GroupContents
  
  const renderDate = (date?: string) => {
      return (
++<<<<<<< HEAD
 +        <Typography noWrap style={{ minWidth: '100px' }}>
++=======
+         <Typography
+             noWrap
+             style={{ minWidth: "100px" }}
+         >
++>>>>>>> main
              {formatDate(date)}
          </Typography>
      );
  };
  
  const renderWorkflowName = (item: WorkflowResource) => (
++<<<<<<< HEAD
 +    <Grid container alignItems='center' wrap='nowrap' spacing={16}>
 +        <Grid item>{renderIcon(item)}</Grid>
 +        <Grid item>
 +            <Typography color='primary' style={{ width: '100px' }}>
++=======
+     <Grid
+         container
+         alignItems="center"
+         wrap="nowrap"
+         spacing={16}
+     >
+         <Grid item>{renderIcon(item)}</Grid>
+         <Grid item>
+             <Typography
+                 color="primary"
+                 style={{ width: "100px" }}
+             >
++>>>>>>> main
                  {item.name}
              </Typography>
          </Grid>
@@@ -155,7 -178,7 +249,11 @@@ const resourceShare = (dispatch: Dispat
      return (
          <div>
              {!isPublic && uuid && (
++<<<<<<< HEAD
 +                <Tooltip title='Share'>
++=======
+                 <Tooltip title="Share">
++>>>>>>> main
                      <IconButton onClick={() => dispatch<any>(openSharingDialog(uuid))}>
                          <ShareIcon />
                      </IconButton>
@@@ -169,8 -192,8 +267,13 @@@ export const ResourceShare = connect((s
      const resource = getResource<WorkflowResource>(props.uuid)(state.resources);
      const uuidPrefix = getUuidPrefix(state);
      return {
++<<<<<<< HEAD
 +        uuid: resource ? resource.uuid : '',
 +        ownerUuid: resource ? resource.ownerUuid : '',
++=======
+         uuid: resource ? resource.uuid : "",
+         ownerUuid: resource ? resource.ownerUuid : "",
++>>>>>>> main
          uuidPrefix,
      };
  })((props: { ownerUuid?: string; uuidPrefix: string; uuid?: string } & DispatchProp<any>) =>
@@@ -184,20 -207,25 +287,39 @@@ const renderFirstName = (item: { firstN
  
  export const ResourceFirstName = connect((state: RootState, props: { uuid: string }) => {
      const resource = getResource<UserResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
 +    return resource || { firstName: '' };
++=======
+     return resource || { firstName: "" };
++>>>>>>> main
  })(renderFirstName);
  
  const renderLastName = (item: { lastName: string }) => <Typography noWrap>{item.lastName}</Typography>;
  
  export const ResourceLastName = connect((state: RootState, props: { uuid: string }) => {
      const resource = getResource<UserResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
 +    return resource || { lastName: '' };
 +})(renderLastName);
 +
 +const renderFullName = (dispatch: Dispatch, item: { uuid: string; firstName: string; lastName: string }, link?: boolean) => {
 +    const displayName = (item.firstName + ' ' + item.lastName).trim() || item.uuid;
 +    return link ? (
 +        <Typography noWrap color='primary' style={{ cursor: 'pointer' }} onClick={() => dispatch<any>(navigateToUserProfile(item.uuid))}>
++=======
+     return resource || { lastName: "" };
+ })(renderLastName);
+ 
+ const renderFullName = (dispatch: Dispatch, item: { uuid: string; firstName: string; lastName: string }, link?: boolean) => {
+     const displayName = (item.firstName + " " + item.lastName).trim() || item.uuid;
+     return link ? (
+         <Typography
+             noWrap
+             color="primary"
+             style={{ cursor: "pointer" }}
+             onClick={() => dispatch<any>(navigateToUserProfile(item.uuid))}
+         >
++>>>>>>> main
              {displayName}
          </Typography>
      ) : (
@@@ -207,51 -235,65 +329,107 @@@
  
  export const UserResourceFullName = connect((state: RootState, props: { uuid: string; link?: boolean }) => {
      const resource = getResource<UserResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
 +    return { item: resource || { uuid: '', firstName: '', lastName: '' }, link: props.link };
++=======
+     return { item: resource || { uuid: "", firstName: "", lastName: "" }, link: props.link };
++>>>>>>> main
  })((props: { item: { uuid: string; firstName: string; lastName: string }; link?: boolean } & DispatchProp<any>) =>
      renderFullName(props.dispatch, props.item, props.link)
  );
  
  const renderUuid = (item: { uuid: string }) => (
++<<<<<<< HEAD
 +    <Typography data-cy='uuid' noWrap>
 +        {item.uuid}
 +        {(item.uuid && <CopyToClipboardSnackbar value={item.uuid} />) || '-'}
++=======
+     <Typography
+         data-cy="uuid"
+         noWrap
+     >
+         {item.uuid}
+         {(item.uuid && <CopyToClipboardSnackbar value={item.uuid} />) || "-"}
++>>>>>>> main
      </Typography>
  );
  
  const renderUuidCopyIcon = (item: { uuid: string }) => (
++<<<<<<< HEAD
 +    <Typography data-cy='uuid' noWrap>
 +        {(item.uuid && <CopyToClipboardSnackbar value={item.uuid} />) || '-'}
 +    </Typography>
 +);
 +
 +export const ResourceUuid = connect((state: RootState, props: { uuid: string }) => getResource<UserResource>(props.uuid)(state.resources) || { uuid: '' })(renderUuid);
++=======
+     <Typography
+         data-cy="uuid"
+         noWrap
+     >
+         {(item.uuid && <CopyToClipboardSnackbar value={item.uuid} />) || "-"}
+     </Typography>
+ );
+ 
+ export const ResourceUuid = connect(
+     (state: RootState, props: { uuid: string }) => getResource<UserResource>(props.uuid)(state.resources) || { uuid: "" }
+ )(renderUuid);
++>>>>>>> main
  
  const renderEmail = (item: { email: string }) => <Typography noWrap>{item.email}</Typography>;
  
  export const ResourceEmail = connect((state: RootState, props: { uuid: string }) => {
      const resource = getResource<UserResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
 +    return resource || { email: '' };
 +})(renderEmail);
 +
 +enum UserAccountStatus {
 +    ACTIVE = 'Active',
 +    INACTIVE = 'Inactive',
 +    SETUP = 'Setup',
 +    UNKNOWN = '',
 +}
 +
 +const renderAccountStatus = (props: { status: UserAccountStatus }) => (
 +    <Grid container alignItems='center' wrap='nowrap' spacing={8} data-cy='account-status'>
++=======
+     return resource || { email: "" };
+ })(renderEmail);
+ 
+ enum UserAccountStatus {
+     ACTIVE = "Active",
+     INACTIVE = "Inactive",
+     SETUP = "Setup",
+     UNKNOWN = "",
+ }
+ 
+ const renderAccountStatus = (props: { status: UserAccountStatus }) => (
+     <Grid
+         container
+         alignItems="center"
+         wrap="nowrap"
+         spacing={8}
+         data-cy="account-status"
+     >
++>>>>>>> main
          <Grid item>
              {(() => {
                  switch (props.status) {
                      case UserAccountStatus.ACTIVE:
++<<<<<<< HEAD
 +                        return <ActiveIcon style={{ color: '#4caf50', verticalAlign: 'middle' }} />;
 +                    case UserAccountStatus.SETUP:
 +                        return <SetupIcon style={{ color: '#2196f3', verticalAlign: 'middle' }} />;
 +                    case UserAccountStatus.INACTIVE:
 +                        return <InactiveIcon style={{ color: '#9e9e9e', verticalAlign: 'middle' }} />;
++=======
+                         return <ActiveIcon style={{ color: "#4caf50", verticalAlign: "middle" }} />;
+                     case UserAccountStatus.SETUP:
+                         return <SetupIcon style={{ color: "#2196f3", verticalAlign: "middle" }} />;
+                     case UserAccountStatus.INACTIVE:
+                         return <InactiveIcon style={{ color: "#9e9e9e", verticalAlign: "middle" }} />;
++>>>>>>> main
                      default:
                          return <></>;
                  }
@@@ -303,11 -345,11 +481,19 @@@ const renderIsHidden = (props: 
      if (props.memberLinkUuid) {
          return (
              <Checkbox
++<<<<<<< HEAD
 +                data-cy='user-visible-checkbox'
 +                color='primary'
 +                checked={props.visible}
 +                disabled={!props.canManage}
 +                onClick={(e) => {
++=======
+                 data-cy="user-visible-checkbox"
+                 color="primary"
+                 checked={props.visible}
+                 disabled={!props.canManage}
+                 onClick={e => {
++>>>>>>> main
                      e.stopPropagation();
                      props.setMemberIsHidden(props.memberLinkUuid, props.permissionLinkUuid, !props.visible);
                  }}
@@@ -339,16 -381,16 +525,20 @@@ export const ResourceLinkTailIsVisible 
  
          return member?.kind === ResourceKind.USER
              ? { memberLinkUuid: link?.uuid, permissionLinkUuid, visible: isVisible, canManage: !isBuiltin }
++<<<<<<< HEAD
 +            : { memberLinkUuid: '', permissionLinkUuid: '', visible: false, canManage: false };
++=======
+             : { memberLinkUuid: "", permissionLinkUuid: "", visible: false, canManage: false };
++>>>>>>> main
      },
      { setMemberIsHidden }
  )(renderIsHidden);
  
  const renderIsAdmin = (props: { uuid: string; isAdmin: boolean; toggleIsAdmin: (uuid: string) => void }) => (
      <Checkbox
 -        color="primary"
 +        color='primary'
          checked={props.isAdmin}
-         onClick={(e) => {
+         onClick={e => {
              e.stopPropagation();
              props.toggleIsAdmin(props.uuid);
          }}
@@@ -367,7 -409,7 +557,11 @@@ const renderUsername = (item: { usernam
  
  export const ResourceUsername = connect((state: RootState, props: { uuid: string }) => {
      const resource = getResource<UserResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
 +    return resource || { username: '', uuid: props.uuid };
++=======
+     return resource || { username: "", uuid: props.uuid };
++>>>>>>> main
  })(renderUsername);
  
  // Virtual machine resource
@@@ -376,16 -418,16 +570,26 @@@ const renderHostname = (item: { hostnam
  
  export const VirtualMachineHostname = connect((state: RootState, props: { uuid: string }) => {
      const resource = getResource<VirtualMachinesResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
 +    return resource || { hostname: '' };
++=======
+     return resource || { hostname: "" };
++>>>>>>> main
  })(renderHostname);
  
  const renderVirtualMachineLogin = (login: { user: string }) => <Typography noWrap>{login.user}</Typography>;
  
  export const VirtualMachineLogin = connect((state: RootState, props: { linkUuid: string }) => {
      const permission = getResource<LinkResource>(props.linkUuid)(state.resources);
++<<<<<<< HEAD
 +    const user = getResource<UserResource>(permission?.tailUuid || '')(state.resources);
 +
 +    return { user: user?.username || permission?.tailUuid || '' };
++=======
+     const user = getResource<UserResource>(permission?.tailUuid || "")(state.resources);
+ 
+     return { user: user?.username || permission?.tailUuid || "" };
++>>>>>>> main
  })(renderVirtualMachineLogin);
  
  // Common methods
@@@ -393,42 -435,43 +597,59 @@@ const renderCommonData = (data: string
  
  const renderCommonDate = (date: string) => <Typography noWrap>{formatDate(date)}</Typography>;
  
- export const CommonUuid = withResourceData('uuid', renderCommonData);
+ export const CommonUuid = withResourceData("uuid", renderCommonData);
  
  // Api Client Authorizations
- export const TokenApiClientId = withResourceData('apiClientId', renderCommonData);
+ export const TokenApiClientId = withResourceData("apiClientId", renderCommonData);
  
- export const TokenApiToken = withResourceData('apiToken', renderCommonData);
+ export const TokenApiToken = withResourceData("apiToken", renderCommonData);
  
- export const TokenCreatedByIpAddress = withResourceData('createdByIpAddress', renderCommonDate);
+ export const TokenCreatedByIpAddress = withResourceData("createdByIpAddress", renderCommonDate);
  
- export const TokenDefaultOwnerUuid = withResourceData('defaultOwnerUuid', renderCommonData);
+ export const TokenDefaultOwnerUuid = withResourceData("defaultOwnerUuid", renderCommonData);
  
- export const TokenExpiresAt = withResourceData('expiresAt', renderCommonDate);
+ export const TokenExpiresAt = withResourceData("expiresAt", renderCommonDate);
  
- export const TokenLastUsedAt = withResourceData('lastUsedAt', renderCommonDate);
+ export const TokenLastUsedAt = withResourceData("lastUsedAt", renderCommonDate);
  
- export const TokenLastUsedByIpAddress = withResourceData('lastUsedByIpAddress', renderCommonData);
+ export const TokenLastUsedByIpAddress = withResourceData("lastUsedByIpAddress", renderCommonData);
  
- export const TokenScopes = withResourceData('scopes', renderCommonData);
+ export const TokenScopes = withResourceData("scopes", renderCommonData);
  
- export const TokenUserId = withResourceData('userId', renderCommonData);
+ export const TokenUserId = withResourceData("userId", renderCommonData);
  
  const clusterColors = [
++<<<<<<< HEAD
 +    ['#f44336', '#fff'],
 +    ['#2196f3', '#fff'],
 +    ['#009688', '#fff'],
 +    ['#cddc39', '#fff'],
 +    ['#ff9800', '#fff'],
++=======
+     ["#f44336", "#fff"],
+     ["#2196f3", "#fff"],
+     ["#009688", "#fff"],
+     ["#cddc39", "#fff"],
+     ["#ff9800", "#fff"],
++>>>>>>> main
  ];
  
  export const ResourceCluster = (props: { uuid: string }) => {
      const CLUSTER_ID_LENGTH = 5;
++<<<<<<< HEAD
 +    const pos = props.uuid.length > CLUSTER_ID_LENGTH ? props.uuid.indexOf('-') : 5;
 +    const clusterId = pos >= CLUSTER_ID_LENGTH ? props.uuid.substring(0, pos) : '';
 +    const ci =
 +        pos >= CLUSTER_ID_LENGTH
 +            ? ((props.uuid.charCodeAt(0) * props.uuid.charCodeAt(1) + props.uuid.charCodeAt(2)) * props.uuid.charCodeAt(3) + props.uuid.charCodeAt(4)) %
++=======
+     const pos = props.uuid.length > CLUSTER_ID_LENGTH ? props.uuid.indexOf("-") : 5;
+     const clusterId = pos >= CLUSTER_ID_LENGTH ? props.uuid.substring(0, pos) : "";
+     const ci =
+         pos >= CLUSTER_ID_LENGTH
+             ? ((props.uuid.charCodeAt(0) * props.uuid.charCodeAt(1) + props.uuid.charCodeAt(2)) * props.uuid.charCodeAt(3) +
+                   props.uuid.charCodeAt(4)) %
++>>>>>>> main
                clusterColors.length
              : 0;
      return (
@@@ -436,7 -479,7 +657,11 @@@
              style={{
                  backgroundColor: clusterColors[ci][0],
                  color: clusterColors[ci][1],
++<<<<<<< HEAD
 +                padding: '2px 7px',
++=======
+                 padding: "2px 7px",
++>>>>>>> main
                  borderRadius: 3,
              }}
          >
@@@ -446,22 -489,22 +671,38 @@@
  };
  
  // Links Resources
++<<<<<<< HEAD
 +const renderLinkName = (item: { name: string }) => <Typography noWrap>{item.name || '-'}</Typography>;
 +
 +export const ResourceLinkName = connect((state: RootState, props: { uuid: string }) => {
 +    const resource = getResource<LinkResource>(props.uuid)(state.resources);
 +    return resource || { name: '' };
++=======
+ const renderLinkName = (item: { name: string }) => <Typography noWrap>{item.name || "-"}</Typography>;
+ 
+ export const ResourceLinkName = connect((state: RootState, props: { uuid: string }) => {
+     const resource = getResource<LinkResource>(props.uuid)(state.resources);
+     return resource || { name: "" };
++>>>>>>> main
  })(renderLinkName);
  
  const renderLinkClass = (item: { linkClass: string }) => <Typography noWrap>{item.linkClass}</Typography>;
  
  export const ResourceLinkClass = connect((state: RootState, props: { uuid: string }) => {
      const resource = getResource<LinkResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
 +    return resource || { linkClass: '' };
 +})(renderLinkClass);
 +
 +const getResourceDisplayName = (resource: Resource): string => {
 +    if ((resource as UserResource).kind === ResourceKind.USER && typeof (resource as UserResource).firstName !== 'undefined') {
++=======
+     return resource || { linkClass: "" };
+ })(renderLinkClass);
+ 
+ const getResourceDisplayName = (resource: Resource): string => {
+     if ((resource as UserResource).kind === ResourceKind.USER && typeof (resource as UserResource).firstName !== "undefined") {
++>>>>>>> main
          // We can be sure the resource is UserResource
          return getUserDisplayName(resource as UserResource);
      } else {
@@@ -473,60 -516,77 +714,120 @@@ const renderResourceLink = (dispatch: D
      var displayName = getResourceDisplayName(item);
  
      return (
++<<<<<<< HEAD
 +        <Typography noWrap color='primary' style={{ cursor: 'pointer' }} onClick={() => dispatch<any>(navigateTo(item.uuid))}>
 +            {resourceLabel(item.kind, item && item.kind === ResourceKind.GROUP ? (item as GroupResource).groupClass || '' : '')}: {displayName || item.uuid}
++=======
+         <Typography
+             noWrap
+             color="primary"
+             style={{ cursor: "pointer" }}
+             onClick={() => {
+                 console.log(item);
+                 item.kind === ResourceKind.GROUP && (item as GroupResource).groupClass === "role"
+                     ? dispatch<any>(navigateToGroupDetails(item.uuid))
+                     : dispatch<any>(navigateTo(item.uuid));
+             }}
+         >
+             {resourceLabel(item.kind, item && item.kind === ResourceKind.GROUP ? (item as GroupResource).groupClass || "" : "")}:{" "}
+             {displayName || item.uuid}
++>>>>>>> main
          </Typography>
      );
  };
  
  export const ResourceLinkTail = connect((state: RootState, props: { uuid: string }) => {
      const resource = getResource<LinkResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
 +    const tailResource = getResource<Resource>(resource?.tailUuid || '')(state.resources);
 +
 +    return {
 +        item: tailResource || { uuid: resource?.tailUuid || '', kind: resource?.tailKind || ResourceKind.NONE },
++=======
+     const tailResource = getResource<Resource>(resource?.tailUuid || "")(state.resources);
+ 
+     return {
+         item: tailResource || { uuid: resource?.tailUuid || "", kind: resource?.tailKind || ResourceKind.NONE },
++>>>>>>> main
      };
  })((props: { item: Resource } & DispatchProp<any>) => renderResourceLink(props.dispatch, props.item));
  
  export const ResourceLinkHead = connect((state: RootState, props: { uuid: string }) => {
      const resource = getResource<LinkResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
 +    const headResource = getResource<Resource>(resource?.headUuid || '')(state.resources);
 +
 +    return {
 +        item: headResource || { uuid: resource?.headUuid || '', kind: resource?.headKind || ResourceKind.NONE },
++=======
+     const headResource = getResource<Resource>(resource?.headUuid || "")(state.resources);
+ 
+     return {
+         item: headResource || { uuid: resource?.headUuid || "", kind: resource?.headKind || ResourceKind.NONE },
++>>>>>>> main
      };
  })((props: { item: Resource } & DispatchProp<any>) => renderResourceLink(props.dispatch, props.item));
  
  export const ResourceLinkUuid = connect((state: RootState, props: { uuid: string }) => {
      const resource = getResource<LinkResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
 +    return resource || { uuid: '' };
++=======
+     return resource || { uuid: "" };
++>>>>>>> main
  })(renderUuid);
  
  export const ResourceLinkHeadUuid = connect((state: RootState, props: { uuid: string }) => {
      const link = getResource<LinkResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
 +    const headResource = getResource<Resource>(link?.headUuid || '')(state.resources);
 +
 +    return headResource || { uuid: '' };
++=======
+     const headResource = getResource<Resource>(link?.headUuid || "")(state.resources);
+ 
+     return headResource || { uuid: "" };
++>>>>>>> main
  })(renderUuid);
  
  export const ResourceLinkTailUuid = connect((state: RootState, props: { uuid: string }) => {
      const link = getResource<LinkResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
 +    const tailResource = getResource<Resource>(link?.tailUuid || '')(state.resources);
 +
 +    return tailResource || { uuid: '' };
++=======
+     const tailResource = getResource<Resource>(link?.tailUuid || "")(state.resources);
+ 
+     return tailResource || { uuid: "" };
++>>>>>>> main
  })(renderUuid);
  
  const renderLinkDelete = (dispatch: Dispatch, item: LinkResource, canManage: boolean) => {
      if (item.uuid) {
          return canManage ? (
              <Typography noWrap>
++<<<<<<< HEAD
 +                <IconButton data-cy='resource-delete-button' onClick={() => dispatch<any>(openRemoveGroupMemberDialog(item.uuid))}>
++=======
+                 <IconButton
+                     data-cy="resource-delete-button"
+                     onClick={() => dispatch<any>(openRemoveGroupMemberDialog(item.uuid))}
+                 >
++>>>>>>> main
                      <RemoveIcon />
                  </IconButton>
              </Typography>
          ) : (
              <Typography noWrap>
++<<<<<<< HEAD
 +                <IconButton disabled data-cy='resource-delete-button'>
++=======
+                 <IconButton
+                     disabled
+                     data-cy="resource-delete-button"
+                 >
++>>>>>>> main
                      <RemoveIcon />
                  </IconButton>
              </Typography>
@@@ -538,26 -598,26 +839,45 @@@
  
  export const ResourceLinkDelete = connect((state: RootState, props: { uuid: string }) => {
      const link = getResource<LinkResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
 +    const isBuiltin = isBuiltinGroup(link?.headUuid || '') || isBuiltinGroup(link?.tailUuid || '');
 +
 +    return {
 +        item: link || { uuid: '', kind: ResourceKind.NONE },
++=======
+     const isBuiltin = isBuiltinGroup(link?.headUuid || "") || isBuiltinGroup(link?.tailUuid || "");
+ 
+     return {
+         item: link || { uuid: "", kind: ResourceKind.NONE },
++>>>>>>> main
          canManage: link && getResourceLinkCanManage(state, link) && !isBuiltin,
      };
  })((props: { item: LinkResource; canManage: boolean } & DispatchProp<any>) => renderLinkDelete(props.dispatch, props.item, props.canManage));
  
  export const ResourceLinkTailEmail = connect((state: RootState, props: { uuid: string }) => {
      const link = getResource<LinkResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
 +    const resource = getResource<UserResource>(link?.tailUuid || '')(state.resources);
 +
 +    return resource || { email: '' };
++=======
+     const resource = getResource<UserResource>(link?.tailUuid || "")(state.resources);
+ 
+     return resource || { email: "" };
++>>>>>>> main
  })(renderEmail);
  
  export const ResourceLinkTailUsername = connect((state: RootState, props: { uuid: string }) => {
      const link = getResource<LinkResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
 +    const resource = getResource<UserResource>(link?.tailUuid || '')(state.resources);
 +
 +    return resource || { username: '' };
++=======
+     const resource = getResource<UserResource>(link?.tailUuid || "")(state.resources);
+ 
+     return resource || { username: "" };
++>>>>>>> main
  })(renderUsername);
  
  const renderPermissionLevel = (dispatch: Dispatch, link: LinkResource, canManage: boolean) => {
@@@ -565,11 -625,14 +885,22 @@@
          <Typography noWrap>
              {formatPermissionLevel(link.name as PermissionLevel)}
              {canManage ? (
++<<<<<<< HEAD
 +                <IconButton data-cy='edit-permission-button' onClick={(event) => dispatch<any>(openPermissionEditContextMenu(event, link))}>
 +                    <RenameIcon />
 +                </IconButton>
 +            ) : (
 +                ''
++=======
+                 <IconButton
+                     data-cy="edit-permission-button"
+                     onClick={event => dispatch<any>(openPermissionEditContextMenu(event, link))}
+                 >
+                     <RenameIcon />
+                 </IconButton>
+             ) : (
+                 ""
++>>>>>>> main
              )}
          </Typography>
      );
@@@ -577,20 -640,20 +908,34 @@@
  
  export const ResourceLinkHeadPermissionLevel = connect((state: RootState, props: { uuid: string }) => {
      const link = getResource<LinkResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
 +    const isBuiltin = isBuiltinGroup(link?.headUuid || '') || isBuiltinGroup(link?.tailUuid || '');
 +
 +    return {
 +        link: link || { uuid: '', name: '', kind: ResourceKind.NONE },
++=======
+     const isBuiltin = isBuiltinGroup(link?.headUuid || "") || isBuiltinGroup(link?.tailUuid || "");
+ 
+     return {
+         link: link || { uuid: "", name: "", kind: ResourceKind.NONE },
++>>>>>>> main
          canManage: link && getResourceLinkCanManage(state, link) && !isBuiltin,
      };
  })((props: { link: LinkResource; canManage: boolean } & DispatchProp<any>) => renderPermissionLevel(props.dispatch, props.link, props.canManage));
  
  export const ResourceLinkTailPermissionLevel = connect((state: RootState, props: { uuid: string }) => {
      const link = getResource<LinkResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
 +    const isBuiltin = isBuiltinGroup(link?.headUuid || '') || isBuiltinGroup(link?.tailUuid || '');
 +
 +    return {
 +        link: link || { uuid: '', name: '', kind: ResourceKind.NONE },
++=======
+     const isBuiltin = isBuiltinGroup(link?.headUuid || "") || isBuiltinGroup(link?.tailUuid || "");
+ 
+     return {
+         link: link || { uuid: "", name: "", kind: ResourceKind.NONE },
++>>>>>>> main
          canManage: link && getResourceLinkCanManage(state, link) && !isBuiltin,
      };
  })((props: { link: LinkResource; canManage: boolean } & DispatchProp<any>) => renderPermissionLevel(props.dispatch, props.link, props.canManage));
@@@ -613,7 -673,7 +955,11 @@@ const resourceRunProcess = (dispatch: D
      return (
          <div>
              {uuid && (
++<<<<<<< HEAD
 +                <Tooltip title='Run process'>
++=======
+                 <Tooltip title="Run process">
++>>>>>>> main
                      <IconButton onClick={() => dispatch<any>(openRunProcess(uuid))}>
                          <ProcessIcon />
                      </IconButton>
@@@ -626,7 -686,7 +972,11 @@@
  export const ResourceRunProcess = connect((state: RootState, props: { uuid: string }) => {
      const resource = getResource<WorkflowResource>(props.uuid)(state.resources);
      return {
++<<<<<<< HEAD
 +        uuid: resource ? resource.uuid : '',
++=======
+         uuid: resource ? resource.uuid : "",
++>>>>>>> main
      };
  })((props: { uuid: string } & DispatchProp<any>) => resourceRunProcess(props.dispatch, props.uuid));
  
@@@ -639,7 -699,10 +989,14 @@@ const renderWorkflowStatus = (uuidPrefi
  };
  
  const renderStatus = (status: string) => (
++<<<<<<< HEAD
 +    <Typography noWrap style={{ width: '60px' }}>
++=======
+     <Typography
+         noWrap
+         style={{ width: "60px" }}
+     >
++>>>>>>> main
          {status}
      </Typography>
  );
@@@ -648,32 -711,41 +1005,65 @@@ export const ResourceWorkflowStatus = c
      const resource = getResource<WorkflowResource>(props.uuid)(state.resources);
      const uuidPrefix = getUuidPrefix(state);
      return {
++<<<<<<< HEAD
 +        ownerUuid: resource ? resource.ownerUuid : '',
++=======
+         ownerUuid: resource ? resource.ownerUuid : "",
++>>>>>>> main
          uuidPrefix,
      };
  })((props: { ownerUuid?: string; uuidPrefix: string }) => renderWorkflowStatus(props.uuidPrefix, props.ownerUuid));
  
  export const ResourceContainerUuid = connect((state: RootState, props: { uuid: string }) => {
      const process = getProcess(props.uuid)(state.resources);
++<<<<<<< HEAD
 +    return { uuid: process?.container?.uuid ? process?.container?.uuid : '' };
 +})((props: { uuid: string }) => renderUuid({ uuid: props.uuid }));
 +
 +enum ColumnSelection {
 +    OUTPUT_UUID = 'outputUuid',
 +    LOG_UUID = 'logUuid',
++=======
+     return { uuid: process?.container?.uuid ? process?.container?.uuid : "" };
+ })((props: { uuid: string }) => renderUuid({ uuid: props.uuid }));
+ 
+ enum ColumnSelection {
+     OUTPUT_UUID = "outputUuid",
+     LOG_UUID = "logUuid",
++>>>>>>> main
  }
  
  const renderUuidLinkWithCopyIcon = (dispatch: Dispatch, item: ProcessResource, column: string) => {
      const selectedColumnUuid = item[column];
      return (
++<<<<<<< HEAD
 +        <Grid container alignItems='center' wrap='nowrap'>
 +            <Grid item>
 +                {selectedColumnUuid ? (
 +                    <Typography color='primary' style={{ width: 'auto', cursor: 'pointer' }} noWrap onClick={() => dispatch<any>(navigateTo(selectedColumnUuid))}>
 +                        {selectedColumnUuid}
 +                    </Typography>
 +                ) : (
 +                    '-'
++=======
+         <Grid
+             container
+             alignItems="center"
+             wrap="nowrap"
+         >
+             <Grid item>
+                 {selectedColumnUuid ? (
+                     <Typography
+                         color="primary"
+                         style={{ width: "auto", cursor: "pointer" }}
+                         noWrap
+                         onClick={() => dispatch<any>(navigateTo(selectedColumnUuid))}
+                     >
+                         {selectedColumnUuid}
+                     </Typography>
+                 ) : (
+                     "-"
++>>>>>>> main
                  )}
              </Grid>
              <Grid item>{selectedColumnUuid && renderUuidCopyIcon({ uuid: selectedColumnUuid })}</Grid>
@@@ -693,36 -765,39 +1083,67 @@@ export const ResourceLogUuid = connect(
  
  export const ResourceParentProcess = connect((state: RootState, props: { uuid: string }) => {
      const process = getProcess(props.uuid)(state.resources);
++<<<<<<< HEAD
 +    return { parentProcess: process?.containerRequest?.requestingContainerUuid || '' };
++=======
+     return { parentProcess: process?.containerRequest?.requestingContainerUuid || "" };
++>>>>>>> main
  })((props: { parentProcess: string }) => renderUuid({ uuid: props.parentProcess }));
  
  export const ResourceModifiedByUserUuid = connect((state: RootState, props: { uuid: string }) => {
      const process = getProcess(props.uuid)(state.resources);
++<<<<<<< HEAD
 +    return { userUuid: process?.containerRequest?.modifiedByUserUuid || '' };
++=======
+     return { userUuid: process?.containerRequest?.modifiedByUserUuid || "" };
++>>>>>>> main
  })((props: { userUuid: string }) => renderUuid({ uuid: props.userUuid }));
  
  export const ResourceCreatedAtDate = connect((state: RootState, props: { uuid: string }) => {
      const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
 +    return { date: resource ? resource.createdAt : '' };
++=======
+     return { date: resource ? resource.createdAt : "" };
++>>>>>>> main
  })((props: { date: string }) => renderDate(props.date));
  
  export const ResourceLastModifiedDate = connect((state: RootState, props: { uuid: string }) => {
      const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
 +    return { date: resource ? resource.modifiedAt : '' };
++=======
+     return { date: resource ? resource.modifiedAt : "" };
++>>>>>>> main
  })((props: { date: string }) => renderDate(props.date));
  
  export const ResourceTrashDate = connect((state: RootState, props: { uuid: string }) => {
      const resource = getResource<TrashableResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
 +    return { date: resource ? resource.trashAt : '' };
++=======
+     return { date: resource ? resource.trashAt : "" };
++>>>>>>> main
  })((props: { date: string }) => renderDate(props.date));
  
  export const ResourceDeleteDate = connect((state: RootState, props: { uuid: string }) => {
      const resource = getResource<TrashableResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
 +    return { date: resource ? resource.deleteAt : '' };
 +})((props: { date: string }) => renderDate(props.date));
 +
 +export const renderFileSize = (fileSize?: number) => (
 +    <Typography noWrap style={{ minWidth: '45px' }}>
++=======
+     return { date: resource ? resource.deleteAt : "" };
+ })((props: { date: string }) => renderDate(props.date));
+ 
+ export const renderFileSize = (fileSize?: number) => (
+     <Typography
+         noWrap
+         style={{ minWidth: "45px" }}
+     >
++>>>>>>> main
          {formatFileSize(fileSize)}
      </Typography>
  );
@@@ -731,38 -806,38 +1152,66 @@@ export const ResourceFileSize = connect
      const resource = getResource<CollectionResource>(props.uuid)(state.resources);
  
      if (resource && resource.kind !== ResourceKind.COLLECTION) {
++<<<<<<< HEAD
 +        return { fileSize: '' };
++=======
+         return { fileSize: "" };
++>>>>>>> main
      }
  
      return { fileSize: resource ? resource.fileSizeTotal : 0 };
  })((props: { fileSize?: number }) => renderFileSize(props.fileSize));
  
++<<<<<<< HEAD
 +const renderOwner = (owner: string) => <Typography noWrap>{owner || '-'}</Typography>;
 +
 +export const ResourceOwner = connect((state: RootState, props: { uuid: string }) => {
 +    const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
 +    return { owner: resource ? resource.ownerUuid : '' };
++=======
+ const renderOwner = (owner: string) => <Typography noWrap>{owner || "-"}</Typography>;
+ 
+ export const ResourceOwner = connect((state: RootState, props: { uuid: string }) => {
+     const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
+     return { owner: resource ? resource.ownerUuid : "" };
++>>>>>>> main
  })((props: { owner: string }) => renderOwner(props.owner));
  
  export const ResourceOwnerName = connect((state: RootState, props: { uuid: string }) => {
      const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
      const ownerNameState = state.ownerName;
++<<<<<<< HEAD
 +    const ownerName = ownerNameState.find((it) => it.uuid === resource!.ownerUuid);
++=======
+     const ownerName = ownerNameState.find(it => it.uuid === resource!.ownerUuid);
++>>>>>>> main
      return { owner: ownerName ? ownerName!.name : resource!.ownerUuid };
  })((props: { owner: string }) => renderOwner(props.owner));
  
  export const ResourceUUID = connect((state: RootState, props: { uuid: string }) => {
      const resource = getResource<CollectionResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
 +    return { uuid: resource ? resource.uuid : '' };
 +})((props: { uuid: string }) => renderUuid({ uuid: props.uuid }));
 +
 +const renderVersion = (version: number) => {
 +    return <Typography>{version ?? '-'}</Typography>;
++=======
+     return { uuid: resource ? resource.uuid : "" };
+ })((props: { uuid: string }) => renderUuid({ uuid: props.uuid }));
+ 
+ const renderVersion = (version: number) => {
+     return <Typography>{version ?? "-"}</Typography>;
++>>>>>>> main
  };
  
  export const ResourceVersion = connect((state: RootState, props: { uuid: string }) => {
      const resource = getResource<CollectionResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
 +    return { version: resource ? resource.version : '' };
++=======
+     return { version: resource ? resource.version : "" };
++>>>>>>> main
  })((props: { version: number }) => renderVersion(props.version));
  
  const renderPortableDataHash = (portableDataHash: string | null) => (
@@@ -773,27 -848,27 +1222,47 @@@
                  <CopyToClipboardSnackbar value={portableDataHash} />
              </>
          ) : (
++<<<<<<< HEAD
 +            '-'
++=======
+             "-"
++>>>>>>> main
          )}
      </Typography>
  );
  
  export const ResourcePortableDataHash = connect((state: RootState, props: { uuid: string }) => {
      const resource = getResource<CollectionResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
 +    return { portableDataHash: resource ? resource.portableDataHash : '' };
 +})((props: { portableDataHash: string }) => renderPortableDataHash(props.portableDataHash));
 +
 +const renderFileCount = (fileCount: number) => {
 +    return <Typography>{fileCount ?? '-'}</Typography>;
++=======
+     return { portableDataHash: resource ? resource.portableDataHash : "" };
+ })((props: { portableDataHash: string }) => renderPortableDataHash(props.portableDataHash));
+ 
+ const renderFileCount = (fileCount: number) => {
+     return <Typography>{fileCount ?? "-"}</Typography>;
++>>>>>>> main
  };
  
  export const ResourceFileCount = connect((state: RootState, props: { uuid: string }) => {
      const resource = getResource<CollectionResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
 +    return { fileCount: resource ? resource.fileCount : '' };
 +})((props: { fileCount: number }) => renderFileCount(props.fileCount));
 +
 +const userFromID = connect((state: RootState, props: { uuid: string }) => {
 +    let userFullname = '';
++=======
+     return { fileCount: resource ? resource.fileCount : "" };
+ })((props: { fileCount: number }) => renderFileCount(props.fileCount));
+ 
+ const userFromID = connect((state: RootState, props: { uuid: string }) => {
+     let userFullname = "";
++>>>>>>> main
      const resource = getResource<GroupContentsResource & UserResource>(props.uuid)(state.resources);
  
      if (resource) {
@@@ -806,7 -881,7 +1275,11 @@@
  const ownerFromResourceId = compose(
      connect((state: RootState, props: { uuid: string }) => {
          const childResource = getResource<GroupContentsResource & UserResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
 +        return { uuid: childResource ? (childResource as Resource).ownerUuid : '' };
++=======
+         return { uuid: childResource ? (childResource as Resource).ownerUuid : "" };
++>>>>>>> main
      }),
      userFromID
  );
@@@ -816,17 -891,25 +1289,36 @@@ const _resourceWithName = withStyles
      { withTheme: true }
  )((props: { uuid: string; userFullname: string; dispatch: Dispatch; theme: ArvadosTheme }) => {
      const { uuid, userFullname, dispatch, theme } = props;
++<<<<<<< HEAD
 +    if (userFullname === '') {
 +        dispatch<any>(loadResource(uuid, false));
 +        return (
 +            <Typography style={{ color: theme.palette.primary.main }} inline noWrap>
++=======
+     if (userFullname === "") {
+         dispatch<any>(loadResource(uuid, false));
+         return (
+             <Typography
+                 style={{ color: theme.palette.primary.main }}
+                 inline
+                 noWrap
+             >
++>>>>>>> main
                  {uuid}
              </Typography>
          );
      }
  
      return (
++<<<<<<< HEAD
 +        <Typography style={{ color: theme.palette.primary.main }} inline noWrap>
++=======
+         <Typography
+             style={{ color: theme.palette.primary.main }}
+             inline
+             noWrap
+         >
++>>>>>>> main
              {userFullname} ({uuid})
          </Typography>
      );
@@@ -839,7 -922,7 +1331,11 @@@ export const ResourceWithName = userFro
  export const UserNameFromID = compose(userFromID)((props: { uuid: string; displayAsText?: string; userFullname: string; dispatch: Dispatch }) => {
      const { uuid, userFullname, dispatch } = props;
  
++<<<<<<< HEAD
 +    if (userFullname === '') {
++=======
+     if (userFullname === "") {
++>>>>>>> main
          dispatch<any>(loadResource(uuid, false));
      }
      return <span>{userFullname ? userFullname : uuid}</span>;
@@@ -847,9 -930,9 +1343,15 @@@
  
  export const ResponsiblePerson = compose(
      connect((state: RootState, props: { uuid: string; parentRef: HTMLElement | null }) => {
++<<<<<<< HEAD
 +        let responsiblePersonName: string = '';
 +        let responsiblePersonUUID: string = '';
 +        let responsiblePersonProperty: string = '';
++=======
+         let responsiblePersonName: string = "";
+         let responsiblePersonUUID: string = "";
+         let responsiblePersonProperty: string = "";
++>>>>>>> main
  
          if (state.auth.config.clusterConfig.Collections.ManagedProperties) {
              let index = 0;
@@@ -857,7 -940,7 +1359,11 @@@
  
              while (!responsiblePersonProperty && keys[index]) {
                  const key = keys[index];
++<<<<<<< HEAD
 +                if (state.auth.config.clusterConfig.Collections.ManagedProperties[key].Function === 'original_owner') {
++=======
+                 if (state.auth.config.clusterConfig.Collections.ManagedProperties[key].Function === "original_owner") {
++>>>>>>> main
                      responsiblePersonProperty = key;
                  }
                  index++;
@@@ -882,22 -965,30 +1388,45 @@@
      const { uuid, responsiblePersonName, parentRef, theme } = props;
  
      if (!uuid && parentRef) {
++<<<<<<< HEAD
 +        parentRef.style.display = 'none';
 +        return null;
 +    } else if (parentRef) {
 +        parentRef.style.display = 'block';
++=======
+         parentRef.style.display = "none";
+         return null;
+     } else if (parentRef) {
+         parentRef.style.display = "block";
++>>>>>>> main
      }
  
      if (!responsiblePersonName) {
          return (
++<<<<<<< HEAD
 +            <Typography style={{ color: theme.palette.primary.main }} inline noWrap>
++=======
+             <Typography
+                 style={{ color: theme.palette.primary.main }}
+                 inline
+                 noWrap
+             >
++>>>>>>> main
                  {uuid}
              </Typography>
          );
      }
  
      return (
++<<<<<<< HEAD
 +        <Typography style={{ color: theme.palette.primary.main }} inline noWrap>
++=======
+         <Typography
+             style={{ color: theme.palette.primary.main }}
+             inline
+             noWrap
+         >
++>>>>>>> main
              {responsiblePersonName} ({uuid})
          </Typography>
      );
@@@ -907,19 -998,27 +1436,39 @@@ const renderType = (type: string, subty
  
  export const ResourceType = connect((state: RootState, props: { uuid: string }) => {
      const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
 +    return { type: resource ? resource.kind : '', subtype: resource && resource.kind === ResourceKind.GROUP ? resource.groupClass : '' };
++=======
+     return { type: resource ? resource.kind : "", subtype: resource && resource.kind === ResourceKind.GROUP ? resource.groupClass : "" };
++>>>>>>> main
  })((props: { type: string; subtype: string }) => renderType(props.type, props.subtype));
  
  export const ResourceStatus = connect((state: RootState, props: { uuid: string }) => {
      return { resource: getResource<GroupContentsResource>(props.uuid)(state.resources) };
  })((props: { resource: GroupContentsResource }) =>
++<<<<<<< HEAD
 +    props.resource && props.resource.kind === ResourceKind.COLLECTION ? <CollectionStatus uuid={props.resource.uuid} /> : <ProcessStatus uuid={props.resource.uuid} />
++=======
+     props.resource && props.resource.kind === ResourceKind.COLLECTION ? (
+         <CollectionStatus uuid={props.resource.uuid} />
+     ) : (
+         <ProcessStatus uuid={props.resource.uuid} />
+     )
++>>>>>>> main
  );
  
  export const CollectionStatus = connect((state: RootState, props: { uuid: string }) => {
      return { collection: getResource<CollectionResource>(props.uuid)(state.resources) };
  })((props: { collection: CollectionResource }) =>
++<<<<<<< HEAD
 +    props.collection.uuid !== props.collection.currentVersionUuid ? <Typography>version {props.collection.version}</Typography> : <Typography>head version</Typography>
++=======
+     props.collection.uuid !== props.collection.currentVersionUuid ? (
+         <Typography>version {props.collection.version}</Typography>
+     ) : (
+         <Typography>head version</Typography>
+     )
++>>>>>>> main
  );
  
  export const CollectionName = connect((state: RootState, props: { uuid: string; className?: string }) => {
@@@ -945,7 -1044,7 +1494,11 @@@ export const ProcessStatus = compose
                  height: props.theme.spacing.unit * 3,
                  width: props.theme.spacing.unit * 12,
                  ...getProcessStatusStyles(getProcessStatus(props.process), props.theme),
++<<<<<<< HEAD
 +                fontSize: '0.875rem',
++=======
+                 fontSize: "0.875rem",
++>>>>>>> main
                  borderRadius: props.theme.spacing.unit * 0.625,
              }}
          />
@@@ -956,11 -1055,14 +1509,22 @@@
  
  export const ProcessStartDate = connect((state: RootState, props: { uuid: string }) => {
      const process = getProcess(props.uuid)(state.resources);
++<<<<<<< HEAD
 +    return { date: process && process.container ? process.container.startedAt : '' };
 +})((props: { date: string }) => renderDate(props.date));
 +
 +export const renderRunTime = (time: number) => (
 +    <Typography noWrap style={{ minWidth: '45px' }}>
++=======
+     return { date: process && process.container ? process.container.startedAt : "" };
+ })((props: { date: string }) => renderDate(props.date));
+ 
+ export const renderRunTime = (time: number) => (
+     <Typography
+         noWrap
+         style={{ minWidth: "45px" }}
+     >
++>>>>>>> main
          {formatTime(time, true)}
      </Typography>
  );
diff --cc src/views/process-panel/process-io-card.tsx
index 85265b13,607bdeb7..b5afbf65
--- a/src/views/process-panel/process-io-card.tsx
+++ b/src/views/process-panel/process-io-card.tsx
@@@ -2,8 -2,8 +2,8 @@@
  //
  // SPDX-License-Identifier: AGPL-3.0
  
- import React, { ReactElement, useState } from "react";
 -import React, { ReactElement, memo, useState } from 'react';
 -import { Dispatch } from 'redux';
++import React, { ReactElement, memo, useState } from "react";
 +import { Dispatch } from "redux";
  import {
      StyleRulesCallback,
      WithStyles,
@@@ -47,25 -56,26 +47,26 @@@ import 
      isPrimitiveOfType,
      StringArrayCommandInputParameter,
      StringCommandInputParameter,
 -    getEnumType
++    getEnumType,
  } from "models/workflow";
 -import { CommandOutputParameter } from 'cwlts/mappings/v1.0/CommandOutputParameter';
 -import { File } from 'models/workflow';
 -import { getInlineFileUrl } from 'views-components/context-menu/actions/helpers';
 -import { AuthState } from 'store/auth/auth-reducer';
 -import mime from 'mime';
 -import { DefaultView } from 'components/default-view/default-view';
 -import { getNavUrl } from 'routes/routes';
 -import { Link as RouterLink } from 'react-router-dom';
 -import { Link as MuiLink } from '@material-ui/core';
 -import { InputCollectionMount } from 'store/processes/processes-actions';
 -import { connect } from 'react-redux';
 -import { RootState } from 'store/store';
 -import { ProcessOutputCollectionFiles } from './process-output-collection-files';
 -import { Process } from 'store/processes/process';
 -import { navigateTo } from 'store/navigation/navigation-action';
 -import classNames from 'classnames';
 -import { DefaultCodeSnippet } from 'components/default-code-snippet/default-code-snippet';
 -import { KEEP_URL_REGEX } from 'models/resource';
 +import { CommandOutputParameter } from "cwlts/mappings/v1.0/CommandOutputParameter";
 +import { File } from "models/workflow";
 +import { getInlineFileUrl } from "views-components/context-menu/actions/helpers";
 +import { AuthState } from "store/auth/auth-reducer";
 +import mime from "mime";
 +import { DefaultView } from "components/default-view/default-view";
 +import { getNavUrl } from "routes/routes";
 +import { Link as RouterLink } from "react-router-dom";
 +import { Link as MuiLink } from "@material-ui/core";
 +import { InputCollectionMount } from "store/processes/processes-actions";
 +import { connect } from "react-redux";
 +import { RootState } from "store/store";
 +import { ProcessOutputCollectionFiles } from "./process-output-collection-files";
 +import { Process } from "store/processes/process";
 +import { navigateTo } from "store/navigation/navigation-action";
 +import classNames from "classnames";
 +import { DefaultCodeSnippet } from "components/default-code-snippet/default-code-snippet";
 +import { KEEP_URL_REGEX } from "models/resource";
  
  type CssRules =
      | "card"
@@@ -231,239 -241,128 +232,253 @@@ const mapDispatchToProps = (dispatch: D
  
  type ProcessIOCardProps = ProcessIOCardDataProps & ProcessIOCardActionProps & WithStyles<CssRules> & MPVPanelProps;
  
 -export const ProcessIOCard = withStyles(styles)(connect(null, mapDispatchToProps)(
 -    ({ classes, label, params, raw, mounts, outputUuid, doHidePanel, doMaximizePanel,
 -        doUnMaximizePanel, panelMaximized, panelName, process, navigateTo, showParams }: ProcessIOCardProps) => {
 -        const [mainProcTabState, setMainProcTabState] = useState(0);
 -        const [subProcTabState, setSubProcTabState] = useState(0);
 -        const handleMainProcTabChange = (event: React.MouseEvent<HTMLElement>, value: number) => {
 -            setMainProcTabState(value);
 -        }
 -        const handleSubProcTabChange = (event: React.MouseEvent<HTMLElement>, value: number) => {
 -            setSubProcTabState(value);
 -        }
 -
 -        const [showImagePreview, setShowImagePreview] = useState(false);
 -
 -        const PanelIcon = label === ProcessIOCardType.INPUT ? InputIcon : OutputIcon;
 -        const mainProcess = !(process && process!.containerRequest.requestingContainerUuid);
 -
 -        const loading = raw === null || raw === undefined || params === null;
 -        const hasRaw = !!(raw && Object.keys(raw).length > 0);
 -        const hasParams = !!(params && params.length > 0);
 -
 -        // Subprocess
 -        const hasInputMounts = !!(label === ProcessIOCardType.INPUT && mounts && mounts.length);
 -        const hasOutputCollecton = !!(label === ProcessIOCardType.OUTPUT && outputUuid);
 -
 -        return <Card className={classes.card} data-cy="process-io-card">
 -            <CardHeader
 -                className={classes.header}
 -                classes={{
 -                    content: classes.title,
 -                    avatar: classes.avatar,
 -                }}
 -                avatar={<PanelIcon className={classes.iconHeader} />}
 -                title={
 -                    <Typography noWrap variant='h6' color='inherit'>
 -                        {label}
 -                    </Typography>
 -                }
 -                action={
 -                    <div>
 -                        {mainProcess && <Tooltip title={"Toggle Image Preview"} disableFocusListener>
 -                            <IconButton data-cy="io-preview-image-toggle" onClick={() => { setShowImagePreview(!showImagePreview) }}>{showImagePreview ? <ImageIcon /> : <ImageOffIcon />}</IconButton>
 -                        </Tooltip>}
 -                        {doUnMaximizePanel && panelMaximized &&
 -                            <Tooltip title={`Unmaximize ${panelName || 'panel'}`} disableFocusListener>
 -                                <IconButton onClick={doUnMaximizePanel}><UnMaximizeIcon /></IconButton>
 -                            </Tooltip>}
 -                        {doMaximizePanel && !panelMaximized &&
 -                            <Tooltip title={`Maximize ${panelName || 'panel'}`} disableFocusListener>
 -                                <IconButton onClick={doMaximizePanel}><MaximizeIcon /></IconButton>
 -                            </Tooltip>}
 -                        {doHidePanel &&
 -                            <Tooltip title={`Close ${panelName || 'panel'}`} disableFocusListener>
 -                                <IconButton disabled={panelMaximized} onClick={doHidePanel}><CloseIcon /></IconButton>
 -                            </Tooltip>}
 -                    </div>
 -                } />
 -            <CardContent className={classes.content}>
 -                {(mainProcess || showParams) ?
 -                    (<>
 -                        {/* raw is undefined until params are loaded */}
 -                        {loading && <Grid container item alignItems='center' justify='center'>
 -                            <CircularProgress />
 -                        </Grid>}
 -                        {/* Once loaded, either raw or params may still be empty
 -			  *   Raw when all params are empty
 -			  *   Params when raw is provided by containerRequest properties but workflow mount is absent for preview
 -			  */}
 -                        {(!loading && (hasRaw || hasParams)) &&
 +export const ProcessIOCard = withStyles(styles)(
 +    connect(
 +        null,
 +        mapDispatchToProps
 +    )(
 +        ({
 +            classes,
 +            label,
 +            params,
 +            raw,
 +            mounts,
 +            outputUuid,
 +            doHidePanel,
 +            doMaximizePanel,
 +            doUnMaximizePanel,
 +            panelMaximized,
 +            panelName,
 +            process,
 +            navigateTo,
 +            showParams,
 +        }: ProcessIOCardProps) => {
 +            const [mainProcTabState, setMainProcTabState] = useState(0);
 +            const [subProcTabState, setSubProcTabState] = useState(0);
 +            const handleMainProcTabChange = (event: React.MouseEvent<HTMLElement>, value: number) => {
 +                setMainProcTabState(value);
 +            };
 +            const handleSubProcTabChange = (event: React.MouseEvent<HTMLElement>, value: number) => {
 +                setSubProcTabState(value);
 +            };
 +
 +            const [showImagePreview, setShowImagePreview] = useState(false);
 +
 +            const PanelIcon = label === ProcessIOCardType.INPUT ? InputIcon : OutputIcon;
 +            const mainProcess = !(process && process!.containerRequest.requestingContainerUuid);
 +
 +            const loading = raw === null || raw === undefined || params === null;
 +            const hasRaw = !!(raw && Object.keys(raw).length > 0);
 +            const hasParams = !!(params && params.length > 0);
 +
 +            // Subprocess
 +            const hasInputMounts = !!(label === ProcessIOCardType.INPUT && mounts && mounts.length);
 +            const hasOutputCollecton = !!(label === ProcessIOCardType.OUTPUT && outputUuid);
 +
 +            return (
 +                <Card
 +                    className={classes.card}
-                     data-cy="process-io-card">
-                     {/* here */}
++                    data-cy="process-io-card"
++                >
 +                    <CardHeader
 +                        className={classes.header}
 +                        classes={{
 +                            content: classes.title,
 +                            avatar: classes.avatar,
 +                        }}
 +                        avatar={<PanelIcon className={classes.iconHeader} />}
 +                        title={
 +                            <Typography
 +                                noWrap
 +                                variant="h6"
-                                 color="inherit">
++                                color="inherit"
++                            >
 +                                {label}
 +                            </Typography>
 +                        }
 +                        action={
 +                            <div>
 +                                {mainProcess && (
 +                                    <Tooltip
 +                                        title={"Toggle Image Preview"}
-                                         disableFocusListener>
++                                        disableFocusListener
++                                    >
 +                                        <IconButton
 +                                            data-cy="io-preview-image-toggle"
 +                                            onClick={() => {
 +                                                setShowImagePreview(!showImagePreview);
-                                             }}>
++                                            }}
++                                        >
 +                                            {showImagePreview ? <ImageIcon /> : <ImageOffIcon />}
 +                                        </IconButton>
 +                                    </Tooltip>
 +                                )}
 +                                {doUnMaximizePanel && panelMaximized && (
 +                                    <Tooltip
 +                                        title={`Unmaximize ${panelName || "panel"}`}
-                                         disableFocusListener>
++                                        disableFocusListener
++                                    >
 +                                        <IconButton onClick={doUnMaximizePanel}>
 +                                            <UnMaximizeIcon />
 +                                        </IconButton>
 +                                    </Tooltip>
 +                                )}
 +                                {doMaximizePanel && !panelMaximized && (
 +                                    <Tooltip
 +                                        title={`Maximize ${panelName || "panel"}`}
-                                         disableFocusListener>
++                                        disableFocusListener
++                                    >
 +                                        <IconButton onClick={doMaximizePanel}>
 +                                            <MaximizeIcon />
 +                                        </IconButton>
 +                                    </Tooltip>
 +                                )}
 +                                {doHidePanel && (
 +                                    <Tooltip
 +                                        title={`Close ${panelName || "panel"}`}
-                                         disableFocusListener>
++                                        disableFocusListener
++                                    >
 +                                        <IconButton
 +                                            disabled={panelMaximized}
-                                             onClick={doHidePanel}>
++                                            onClick={doHidePanel}
++                                        >
 +                                            <CloseIcon />
 +                                        </IconButton>
 +                                    </Tooltip>
 +                                )}
 +                            </div>
 +                        }
 +                    />
 +                    <CardContent className={classes.content}>
 +                        {mainProcess || showParams ? (
                              <>
 -                                <Tabs value={mainProcTabState} onChange={handleMainProcTabChange} variant="fullWidth" className={classes.symmetricTabs}>
 -                                    {/* params will be empty on processes without workflow definitions in mounts, so we only show raw */}
 -                                    {hasParams && <Tab label="Parameters" />}
 -                                    {!showParams && <Tab label="JSON" />}
 -                                </Tabs>
 -                                {(mainProcTabState === 0 && params && hasParams) && <div className={classes.tableWrapper}>
 -                                    <ProcessIOPreview data={params} showImagePreview={showImagePreview} valueLabel={showParams ? "Default value" : "Value"} />
 -                                </div>}
 -                                {(mainProcTabState === 1 || !hasParams) && <div className={classes.tableWrapper}>
 -                                    <ProcessIORaw data={raw} />
 -                                </div>}
 -                            </>}
 -                        {!loading && !hasRaw && !hasParams && <Grid container item alignItems='center' justify='center'>
 -                            <DefaultView messages={["No parameters found"]} />
 -                        </Grid>}
 -                    </>) :
 -                    // Subprocess
 -                    (<>
 -                        {loading && <Grid container item alignItems='center' justify='center'>
 -                            <CircularProgress />
 -                        </Grid>}
 -                        {!loading && (hasInputMounts || hasOutputCollecton || hasRaw) ?
 +                                {/* raw is undefined until params are loaded */}
 +                                {loading && (
 +                                    <Grid
 +                                        container
 +                                        item
 +                                        alignItems="center"
-                                         justify="center">
++                                        justify="center"
++                                    >
 +                                        <CircularProgress />
 +                                    </Grid>
 +                                )}
 +                                {/* Once loaded, either raw or params may still be empty
 +                                 *   Raw when all params are empty
 +                                 *   Params when raw is provided by containerRequest properties but workflow mount is absent for preview
 +                                 */}
 +                                {!loading && (hasRaw || hasParams) && (
 +                                    <>
 +                                        <Tabs
 +                                            value={mainProcTabState}
 +                                            onChange={handleMainProcTabChange}
 +                                            variant="fullWidth"
-                                             className={classes.symmetricTabs}>
++                                            className={classes.symmetricTabs}
++                                        >
 +                                            {/* params will be empty on processes without workflow definitions in mounts, so we only show raw */}
 +                                            {hasParams && <Tab label="Parameters" />}
 +                                            {!showParams && <Tab label="JSON" />}
 +                                        </Tabs>
 +                                        {mainProcTabState === 0 && params && hasParams && (
 +                                            <div className={classes.tableWrapper}>
 +                                                <ProcessIOPreview
 +                                                    data={params}
 +                                                    showImagePreview={showImagePreview}
 +                                                    valueLabel={showParams ? "Default value" : "Value"}
 +                                                />
 +                                            </div>
 +                                        )}
 +                                        {(mainProcTabState === 1 || !hasParams) && (
 +                                            <div className={classes.tableWrapper}>
 +                                                <ProcessIORaw data={raw} />
 +                                            </div>
 +                                        )}
 +                                    </>
 +                                )}
 +                                {!loading && !hasRaw && !hasParams && (
 +                                    <Grid
 +                                        container
 +                                        item
 +                                        alignItems="center"
-                                         justify="center">
++                                        justify="center"
++                                    >
 +                                        <DefaultView messages={["No parameters found"]} />
 +                                    </Grid>
 +                                )}
 +                            </>
 +                        ) : (
 +                            // Subprocess
                              <>
 -                                <Tabs value={subProcTabState} onChange={handleSubProcTabChange} variant="fullWidth" className={classes.symmetricTabs}>
 -                                    {hasInputMounts && <Tab label="Collections" />}
 -                                    {hasOutputCollecton && <Tab label="Collection" />}
 -                                    <Tab label="JSON" />
 -                                </Tabs>
 -                                <div className={classes.tableWrapper}>
 -                                    {subProcTabState === 0 && hasInputMounts && <ProcessInputMounts mounts={mounts || []} />}
 -                                    {subProcTabState === 0 && hasOutputCollecton && <>
 -                                        {outputUuid && <Typography className={classes.collectionLink}>
 -                                            Output Collection: <MuiLink className={classes.keepLink} onClick={() => { navigateTo(outputUuid || "") }}>
 -                                                {outputUuid}
 -                                            </MuiLink></Typography>}
 -                                        <ProcessOutputCollectionFiles isWritable={false} currentItemUuid={outputUuid} />
 -                                    </>}
 -                                    {(subProcTabState === 1 || (!hasInputMounts && !hasOutputCollecton)) && <div className={classes.tableWrapper}>
 -                                        <ProcessIORaw data={raw} />
 -                                    </div>}
 -                                </div>
 -                            </> :
 -                            <Grid container item alignItems='center' justify='center'>
 -                                <DefaultView messages={["No data to display"]} />
 -                            </Grid>
 -                        }
 -                    </>)
 -                }
 -            </CardContent>
 -        </Card>;
 -    }
 -));
 +                                {loading && (
 +                                    <Grid
 +                                        container
 +                                        item
 +                                        alignItems="center"
-                                         justify="center">
++                                        justify="center"
++                                    >
 +                                        <CircularProgress />
 +                                    </Grid>
 +                                )}
 +                                {!loading && (hasInputMounts || hasOutputCollecton || hasRaw) ? (
 +                                    <>
 +                                        <Tabs
 +                                            value={subProcTabState}
 +                                            onChange={handleSubProcTabChange}
 +                                            variant="fullWidth"
-                                             className={classes.symmetricTabs}>
++                                            className={classes.symmetricTabs}
++                                        >
 +                                            {hasInputMounts && <Tab label="Collections" />}
 +                                            {hasOutputCollecton && <Tab label="Collection" />}
 +                                            <Tab label="JSON" />
 +                                        </Tabs>
 +                                        <div className={classes.tableWrapper}>
 +                                            {subProcTabState === 0 && hasInputMounts && <ProcessInputMounts mounts={mounts || []} />}
 +                                            {subProcTabState === 0 && hasOutputCollecton && (
 +                                                <>
 +                                                    {outputUuid && (
 +                                                        <Typography className={classes.collectionLink}>
 +                                                            Output Collection:{" "}
 +                                                            <MuiLink
 +                                                                className={classes.keepLink}
 +                                                                onClick={() => {
 +                                                                    navigateTo(outputUuid || "");
-                                                                 }}>
++                                                                }}
++                                                            >
 +                                                                {outputUuid}
 +                                                            </MuiLink>
 +                                                        </Typography>
 +                                                    )}
 +                                                    <ProcessOutputCollectionFiles
 +                                                        isWritable={false}
 +                                                        currentItemUuid={outputUuid}
 +                                                    />
 +                                                </>
 +                                            )}
 +                                            {(subProcTabState === 1 || (!hasInputMounts && !hasOutputCollecton)) && (
 +                                                <div className={classes.tableWrapper}>
 +                                                    <ProcessIORaw data={raw} />
 +                                                </div>
 +                                            )}
 +                                        </div>
 +                                    </>
 +                                ) : (
 +                                    <Grid
 +                                        container
 +                                        item
 +                                        alignItems="center"
-                                         justify="center">
++                                        justify="center"
++                                    >
 +                                        <DefaultView messages={["No data to display"]} />
 +                                    </Grid>
 +                                )}
 +                            </>
 +                        )}
 +                    </CardContent>
 +                </Card>
 +            );
 +        }
 +    )
 +);
  
  export type ProcessIOValue = {
      display: ReactElement<any, any>;
@@@ -486,77 -385,64 +501,82 @@@ interface ProcessIOPreviewDataProps 
  
  type ProcessIOPreviewProps = ProcessIOPreviewDataProps & WithStyles<CssRules>;
  
- const ProcessIOPreview = withStyles(styles)(({ classes, data, showImagePreview, valueLabel }: ProcessIOPreviewProps) => {
-     const showLabel = data.some((param: ProcessIOParameter) => param.label);
-     return (
-         <Table
-             className={classes.tableRoot}
-             aria-label="Process IO Preview">
-             <TableHead>
-                 <TableRow>
-                     <TableCell>Name</TableCell>
-                     {showLabel && <TableCell className={classes.labelColumn}>Label</TableCell>}
-                     <TableCell>{valueLabel}</TableCell>
-                     <TableCell>Collection</TableCell>
-                 </TableRow>
-             </TableHead>
-             <TableBody>
-                 {data.map((param: ProcessIOParameter) => {
-                     const firstVal = param.value.length > 0 ? param.value[0] : undefined;
-                     const rest = param.value.slice(1);
-                     const mainRowClasses = {
-                         [classes.noBorderRow]: rest.length > 0,
-                     };
- 
-                     return (
-                         <React.Fragment key={param.id}>
-                             <TableRow
-                                 className={classNames(mainRowClasses)}
-                                 data-cy="process-io-param">
-                                 <TableCell>{param.id}</TableCell>
-                                 {showLabel && <TableCell>{param.label}</TableCell>}
-                                 <TableCell>
-                                     {firstVal && (
-                                         <ProcessValuePreview
-                                             value={firstVal}
-                                             showImagePreview={showImagePreview}
-                                         />
-                                     )}
-                                 </TableCell>
-                                 <TableCell className={firstVal?.imageUrl ? classes.rowWithPreview : undefined}>
-                                     <Typography className={classes.paramValue}>{firstVal?.collection}</Typography>
-                                 </TableCell>
-                             </TableRow>
-                             {rest.map((val, i) => {
-                                 const rowClasses = {
-                                     [classes.noBorderRow]: i < rest.length - 1,
-                                     [classes.secondaryRow]: val.secondary,
-                                 };
-                                 return (
-                                     <TableRow
-                                         className={classNames(rowClasses)}
-                                         key={i}>
-                                         <TableCell />
-                                         {showLabel && <TableCell />}
-                                         <TableCell>
 -const ProcessIOPreview = memo(withStyles(styles)(
 -    ({ classes, data, showImagePreview, valueLabel }: ProcessIOPreviewProps) => {
++const ProcessIOPreview = memo(
++    withStyles(styles)(({ classes, data, showImagePreview, valueLabel }: ProcessIOPreviewProps) => {
+         const showLabel = data.some((param: ProcessIOParameter) => param.label);
 -        return <Table className={classes.tableRoot} aria-label="Process IO Preview">
 -            <TableHead>
 -                <TableRow>
 -                    <TableCell>Name</TableCell>
 -                    {showLabel && <TableCell className={classes.labelColumn}>Label</TableCell>}
 -                    <TableCell>{valueLabel}</TableCell>
 -                    <TableCell>Collection</TableCell>
 -                </TableRow>
 -            </TableHead>
 -            <TableBody>
 -                {data.map((param: ProcessIOParameter) => {
 -                    const firstVal = param.value.length > 0 ? param.value[0] : undefined;
 -                    const rest = param.value.slice(1);
 -                    const mainRowClasses = {
 -                        [classes.noBorderRow]: (rest.length > 0),
 -                    };
 -
 -                    return <React.Fragment key={param.id}>
 -                        <TableRow className={classNames(mainRowClasses)} data-cy="process-io-param">
 -                            <TableCell>
 -                                {param.id}
 -                            </TableCell>
 -                            {showLabel && <TableCell >{param.label}</TableCell>}
 -                            <TableCell>
 -                                {firstVal && <ProcessValuePreview value={firstVal} showImagePreview={showImagePreview} />}
 -                            </TableCell>
 -                            <TableCell className={firstVal?.imageUrl ? classes.rowWithPreview : undefined}>
 -                                <Typography className={classes.paramValue}>
 -                                    {firstVal?.collection}
 -                                </Typography>
 -                            </TableCell>
 -                        </TableRow>
 -                        {rest.map((val, i) => {
 -                            const rowClasses = {
 -                                [classes.noBorderRow]: (i < rest.length - 1),
 -                                [classes.secondaryRow]: val.secondary,
 -                            };
 -                            return <TableRow className={classNames(rowClasses)} key={i}>
 -                                <TableCell />
 -                                {showLabel && <TableCell />}
 -                                <TableCell>
 -                                    <ProcessValuePreview value={val} showImagePreview={showImagePreview} />
 -                                </TableCell>
 -                                <TableCell className={firstVal?.imageUrl ? classes.rowWithPreview : undefined}>
 -                                    <Typography className={classes.paramValue}>
 -                                        {val.collection}
 -                                    </Typography>
 -                                </TableCell>
 -                            </TableRow>
 -                        })}
 -                    </React.Fragment>;
 -                })}
 -            </TableBody>
 -        </Table >;
 -    }));
++        return (
++            <Table
++                className={classes.tableRoot}
++                aria-label="Process IO Preview"
++            >
++                <TableHead>
++                    <TableRow>
++                        <TableCell>Name</TableCell>
++                        {showLabel && <TableCell className={classes.labelColumn}>Label</TableCell>}
++                        <TableCell>{valueLabel}</TableCell>
++                        <TableCell>Collection</TableCell>
++                    </TableRow>
++                </TableHead>
++                <TableBody>
++                    {data.map((param: ProcessIOParameter) => {
++                        const firstVal = param.value.length > 0 ? param.value[0] : undefined;
++                        const rest = param.value.slice(1);
++                        const mainRowClasses = {
++                            [classes.noBorderRow]: rest.length > 0,
++                        };
++
++                        return (
++                            <React.Fragment key={param.id}>
++                                <TableRow
++                                    className={classNames(mainRowClasses)}
++                                    data-cy="process-io-param"
++                                >
++                                    <TableCell>{param.id}</TableCell>
++                                    {showLabel && <TableCell>{param.label}</TableCell>}
++                                    <TableCell>
++                                        {firstVal && (
 +                                            <ProcessValuePreview
-                                                 value={val}
++                                                value={firstVal}
 +                                                showImagePreview={showImagePreview}
 +                                            />
-                                         </TableCell>
-                                         <TableCell className={firstVal?.imageUrl ? classes.rowWithPreview : undefined}>
-                                             <Typography className={classes.paramValue}>{val.collection}</Typography>
-                                         </TableCell>
-                                     </TableRow>
-                                 );
-                             })}
-                         </React.Fragment>
-                     );
-                 })}
-             </TableBody>
-         </Table>
-     );
- });
++                                        )}
++                                    </TableCell>
++                                    <TableCell className={firstVal?.imageUrl ? classes.rowWithPreview : undefined}>
++                                        <Typography className={classes.paramValue}>{firstVal?.collection}</Typography>
++                                    </TableCell>
++                                </TableRow>
++                                {rest.map((val, i) => {
++                                    const rowClasses = {
++                                        [classes.noBorderRow]: i < rest.length - 1,
++                                        [classes.secondaryRow]: val.secondary,
++                                    };
++                                    return (
++                                        <TableRow
++                                            className={classNames(rowClasses)}
++                                            key={i}
++                                        >
++                                            <TableCell />
++                                            {showLabel && <TableCell />}
++                                            <TableCell>
++                                                <ProcessValuePreview
++                                                    value={val}
++                                                    showImagePreview={showImagePreview}
++                                                />
++                                            </TableCell>
++                                            <TableCell className={firstVal?.imageUrl ? classes.rowWithPreview : undefined}>
++                                                <Typography className={classes.paramValue}>{val.collection}</Typography>
++                                            </TableCell>
++                                        </TableRow>
++                                    );
++                                })}
++                            </React.Fragment>
++                        );
++                    })}
++                </TableBody>
++            </Table>
++        );
++    })
++);
  
  interface ProcessValuePreviewProps {
      value: ProcessIOValue;
@@@ -598,38 -477,28 +618,40 @@@ interface ProcessInputMountsDataProps 
  
  type ProcessInputMountsProps = ProcessInputMountsDataProps & WithStyles<CssRules>;
  
 -const ProcessInputMounts = withStyles(styles)(connect((state: RootState) => ({
 -    auth: state.auth,
 -}))(({ mounts, classes, auth }: ProcessInputMountsProps & { auth: AuthState }) => (
 -    <Table className={classes.tableRoot} aria-label="Process Input Mounts">
 -        <TableHead>
 -            <TableRow>
 -                <TableCell>Path</TableCell>
 -                <TableCell>Portable Data Hash</TableCell>
 -            </TableRow>
 -        </TableHead>
 -        <TableBody>
 -            {mounts.map(mount => (
 -                <TableRow key={mount.path}>
 -                    <TableCell><pre>{mount.path}</pre></TableCell>
 -                    <TableCell>
 -                        <RouterLink to={getNavUrl(mount.pdh, auth)} className={classes.keepLink}>{mount.pdh}</RouterLink>
 -                    </TableCell>
 +const ProcessInputMounts = withStyles(styles)(
 +    connect((state: RootState) => ({
 +        auth: state.auth,
 +    }))(({ mounts, classes, auth }: ProcessInputMountsProps & { auth: AuthState }) => (
 +        <Table
 +            className={classes.tableRoot}
-             aria-label="Process Input Mounts">
++            aria-label="Process Input Mounts"
++        >
 +            <TableHead>
 +                <TableRow>
 +                    <TableCell>Path</TableCell>
 +                    <TableCell>Portable Data Hash</TableCell>
                  </TableRow>
 -            ))}
 -        </TableBody>
 -    </Table>
 -)));
 +            </TableHead>
 +            <TableBody>
 +                {mounts.map(mount => (
 +                    <TableRow key={mount.path}>
 +                        <TableCell>
 +                            <pre>{mount.path}</pre>
 +                        </TableCell>
 +                        <TableCell>
 +                            <RouterLink
 +                                to={getNavUrl(mount.pdh, auth)}
-                                 className={classes.keepLink}>
++                                className={classes.keepLink}
++                            >
 +                                {mount.pdh}
 +                            </RouterLink>
 +                        </TableCell>
 +                    </TableRow>
 +                ))}
 +            </TableBody>
 +        </Table>
 +    ))
 +);
  
  type FileWithSecondaryFiles = {
      secondaryFiles: File[];
@@@ -677,13 -552,16 +699,13 @@@ export const getIOParamDisplayValue = (
  
          case isPrimitiveOfType(input, CWLType.DIRECTORY):
              const directory = (input as DirectoryCommandInputParameter).value;
 -            return directory !== undefined &&
 -                !(Array.isArray(directory) && directory.length === 0) ?
 -                [directoryToProcessIOValue(directory, auth, pdh)] :
 -                [{ display: <EmptyValue /> }];
 +            return directory !== undefined && !(Array.isArray(directory) && directory.length === 0)
 +                ? [directoryToProcessIOValue(directory, auth, pdh)]
 +                : [{ display: <EmptyValue /> }];
  
-         case typeof input.type === "object" && !(input.type instanceof Array) && input.type.type === "enum":
+         case getEnumType(input) !== null:
              const enumValue = (input as EnumCommandInputParameter).value;
 -            return enumValue !== undefined && enumValue ?
 -                [{ display: <pre>{enumValue}</pre> }] :
 -                [{ display: <EmptyValue /> }];
 +            return enumValue !== undefined && enumValue ? [{ display: <pre>{enumValue}</pre> }] : [{ display: <EmptyValue /> }];
  
          case isArrayOfType(input, CWLType.STRING):
              const strArray = (input as StringArrayCommandInputParameter).value || [];
@@@ -697,26 -579,28 +719,24 @@@
          case isArrayOfType(input, CWLType.FLOAT):
          case isArrayOfType(input, CWLType.DOUBLE):
              const floatArray = (input as FloatArrayCommandInputParameter).value || [];
 -            return floatArray.length ?
 -                [{ display: <>{floatArray.map((val) => renderPrimitiveValue(val, true))}</> }] :
 -                [{ display: <EmptyValue /> }];
 +            return floatArray.length ? [{ display: <>{floatArray.map(val => renderPrimitiveValue(val, true))}</> }] : [{ display: <EmptyValue /> }];
  
          case isArrayOfType(input, CWLType.FILE):
 -            const fileArrayMainFiles = ((input as FileArrayCommandInputParameter).value || []);
 -            const firstMainFilePdh = (fileArrayMainFiles.length > 0 && fileArrayMainFiles[0]) ? getResourcePdhUrl(fileArrayMainFiles[0], pdh) : "";
 +            const fileArrayMainFiles = (input as FileArrayCommandInputParameter).value || [];
 +            const firstMainFilePdh = fileArrayMainFiles.length > 0 && fileArrayMainFiles[0] ? getResourcePdhUrl(fileArrayMainFiles[0], pdh) : "";
  
-             // Convert each main file into separate arrays of ProcessIOValue to preserve secondaryFile grouping
-             const fileArrayValues = fileArrayMainFiles
-                 .map((mainFile: File, i): ProcessIOValue[] => {
-                     const secondaryFiles = (mainFile as unknown as FileWithSecondaryFiles)?.secondaryFiles || [];
-                     return [
-                         // Pass firstMainFilePdh to secondary files and every main file besides the first to hide pdh if equal
-                         ...(mainFile ? [fileToProcessIOValue(mainFile, false, auth, pdh, i > 0 ? firstMainFilePdh : "")] : []),
-                         ...secondaryFiles.map(file => fileToProcessIOValue(file, true, auth, pdh, firstMainFilePdh)),
-                     ];
-                     // Reduce each mainFile/secondaryFile group into single array preserving ordering
-                 })
-                 .reduce((acc: ProcessIOValue[], mainFile: ProcessIOValue[]) => acc.concat(mainFile), []);
+             // Convert each main and secondaryFiles into array of ProcessIOValue preserving ordering
+             let fileArrayValues: ProcessIOValue[] = [];
+             for (let i = 0; i < fileArrayMainFiles.length; i++) {
 -                const secondaryFiles = ((fileArrayMainFiles[i] as unknown) as FileWithSecondaryFiles)?.secondaryFiles || [];
++                const secondaryFiles = (fileArrayMainFiles[i] as unknown as FileWithSecondaryFiles)?.secondaryFiles || [];
+                 fileArrayValues.push(
+                     // Pass firstMainFilePdh to secondary files and every main file besides the first to hide pdh if equal
+                     ...(fileArrayMainFiles[i] ? [fileToProcessIOValue(fileArrayMainFiles[i], false, auth, pdh, i > 0 ? firstMainFilePdh : "")] : []),
 -                    ...(secondaryFiles.map(file => fileToProcessIOValue(file, true, auth, pdh, firstMainFilePdh)))
++                    ...secondaryFiles.map(file => fileToProcessIOValue(file, true, auth, pdh, firstMainFilePdh))
+                 );
+             }
  
 -            return fileArrayValues.length ?
 -                fileArrayValues :
 -                [{ display: <EmptyValue /> }];
 +            return fileArrayValues.length ? fileArrayValues : [{ display: <EmptyValue /> }];
  
          case isArrayOfType(input, CWLType.DIRECTORY):
              const directories = (input as DirectoryArrayCommandInputParameter).value || [];
@@@ -767,38 -648,20 +787,40 @@@ const KeepUrlBase = withStyles(styles)(
      const pdhUrl = getResourcePdhUrl(res, pdh);
      // Passing a pdh always returns a relative wb2 collection url
      const pdhWbPath = getNavUrl(pdhUrl, auth);
 -    return pdhUrl && pdhWbPath ?
 -        <Tooltip title={"View collection in Workbench"}><RouterLink to={pdhWbPath} className={classes.keepLink}>{pdhUrl}</RouterLink></Tooltip> :
 -        <></>;
 +    return pdhUrl && pdhWbPath ? (
 +        <Tooltip title={"View collection in Workbench"}>
 +            <RouterLink
 +                to={pdhWbPath}
-                 className={classes.keepLink}>
++                className={classes.keepLink}
++            >
 +                {pdhUrl}
 +            </RouterLink>
 +        </Tooltip>
 +    ) : (
 +        <></>
 +    );
  });
  
  const KeepUrlPath = withStyles(styles)(({ auth, res, pdh, classes }: KeepUrlProps & WithStyles<CssRules>) => {
      const keepUrl = getKeepUrl(res, pdh);
 -    const keepUrlParts = keepUrl ? keepUrl.split('/') : [];
 -    const keepUrlPath = keepUrlParts.length > 1 ? keepUrlParts.slice(1).join('/') : '';
 +    const keepUrlParts = keepUrl ? keepUrl.split("/") : [];
 +    const keepUrlPath = keepUrlParts.length > 1 ? keepUrlParts.slice(1).join("/") : "";
  
      const keepUrlPathNav = getKeepNavUrl(auth, res, pdh);
 -    return keepUrlPathNav ?
 -        <Tooltip title={"View in keep-web"}><a className={classes.keepLink} href={keepUrlPathNav} target="_blank" rel="noopener noreferrer">{keepUrlPath || '/'}</a></Tooltip> :
 -        <EmptyValue />;
 +    return keepUrlPathNav ? (
 +        <Tooltip title={"View in keep-web"}>
 +            <a
 +                className={classes.keepLink}
 +                href={keepUrlPathNav}
 +                target="_blank"
-                 rel="noopener noreferrer">
++                rel="noopener noreferrer"
++            >
 +                {keepUrlPath || "/"}
 +            </a>
 +        </Tooltip>
 +    ) : (
 +        <EmptyValue />
 +    );
  });
  
  const getKeepNavUrl = (auth: AuthState, file: File | Directory, pdh?: string): string => {
@@@ -867,13 -708,7 +889,14 @@@ const fileToProcessIOValue = (file: Fil
  
      if (isFileUrl(file.location)) {
          return {
 -            display: <MuiLink href={file.location} target="_blank">{file.location}</MuiLink>,
 +            display: (
 +                <MuiLink
 +                    href={file.location}
-                     target="_blank">
++                    target="_blank"
++                >
 +                    {file.location}
 +                </MuiLink>
 +            ),
              secondary,
          };
      }
diff --cc src/views/process-panel/process-panel-root.tsx
index d4c8d937,c04cf62a..d019d141
--- a/src/views/process-panel/process-panel-root.tsx
+++ b/src/views/process-panel/process-panel-root.tsx
@@@ -156,18 -141,17 +157,19 @@@ export const ProcessPanelRoot = withSty
                          lines={getProcessPanelLogs(processLogsPanel)}
                          selectedFilter={{
                              label: processLogsPanel.selectedFilter,
 -                            value: processLogsPanel.selectedFilter
 +                            value: processLogsPanel.selectedFilter,
                          }}
 -                        filters={processLogsPanel.filters.map(
 -                            filter => ({ label: filter, value: filter })
 -                        )}
 +                        filters={processLogsPanel.filters.map(filter => ({ label: filter, value: filter }))}
                          onLogFilterChange={props.onLogFilterChange}
                          navigateToLog={props.navigateToLog}
+                         pollProcessLogs={props.pollProcessLogs}
                      />
                  </MPVPanelContent>
 -                <MPVPanelContent forwardProps xs maxHeight='50%' data-cy="process-inputs">
 +                <MPVPanelContent
 +                    forwardProps
 +                    xs
 +                    maxHeight="50%"
 +                    data-cy="process-inputs">
                      <ProcessIOCard
                          label={ProcessIOCardType.INPUT}
                          process={process}
diff --cc src/views/process-panel/process-panel.tsx
index afad4832,575c6591..f9e02540
--- a/src/views/process-panel/process-panel.tsx
+++ b/src/views/process-panel/process-panel.tsx
@@@ -16,14 -23,14 +16,14 @@@ import 
      loadOutputs,
      toggleProcessPanelFilter,
      updateOutputParams,
 -    loadNodeJson
 -} from 'store/process-panel/process-panel-actions';
 -import { cancelRunningWorkflow, resumeOnHoldWorkflow, startWorkflow } from 'store/processes/processes-actions';
 -import { navigateToLogCollection, pollProcessLogs, setProcessLogsPanelFilter } from 'store/process-logs-panel/process-logs-panel-actions';
 -import { snackbarActions, SnackbarKind } from 'store/snackbar/snackbar-actions';
 +    loadNodeJson,
 +} from "store/process-panel/process-panel-actions";
 +import { cancelRunningWorkflow, resumeOnHoldWorkflow, startWorkflow } from "store/processes/processes-actions";
- import { navigateToLogCollection, setProcessLogsPanelFilter } from "store/process-logs-panel/process-logs-panel-actions";
++import { navigateToLogCollection, pollProcessLogs, setProcessLogsPanelFilter } from "store/process-logs-panel/process-logs-panel-actions";
 +import { snackbarActions, SnackbarKind } from "store/snackbar/snackbar-actions";
  
  const mapStateToProps = ({ router, auth, resources, processPanel, processLogsPanel }: RootState): ProcessPanelRootDataProps => {
 -    const uuid = getProcessPanelCurrentUuid(router) || '';
 +    const uuid = getProcessPanelCurrentUuid(router) || "";
      const subprocesses = getSubprocesses(uuid)(resources);
      return {
          process: getProcess(uuid)(resources),
@@@ -56,16 -61,17 +56,17 @@@ const mapDispatchToProps = (dispatch: D
      onToggle: status => {
          dispatch<any>(toggleProcessPanelFilter(status));
      },
 -    cancelProcess: (uuid) => dispatch<any>(cancelRunningWorkflow(uuid)),
 -    startProcess: (uuid) => dispatch<any>(startWorkflow(uuid)),
 -    resumeOnHoldWorkflow: (uuid) => dispatch<any>(resumeOnHoldWorkflow(uuid)),
 -    onLogFilterChange: (filter) => dispatch(setProcessLogsPanelFilter(filter.value)),
 -    navigateToLog: (uuid) => dispatch<any>(navigateToLogCollection(uuid)),
 -    loadInputs: (containerRequest) => dispatch<any>(loadInputs(containerRequest)),
 -    loadOutputs: (containerRequest) => dispatch<any>(loadOutputs(containerRequest)),
 -    loadOutputDefinitions: (containerRequest) => dispatch<any>(loadOutputDefinitions(containerRequest)),
 +    cancelProcess: uuid => dispatch<any>(cancelRunningWorkflow(uuid)),
 +    startProcess: uuid => dispatch<any>(startWorkflow(uuid)),
 +    resumeOnHoldWorkflow: uuid => dispatch<any>(resumeOnHoldWorkflow(uuid)),
 +    onLogFilterChange: filter => dispatch(setProcessLogsPanelFilter(filter.value)),
 +    navigateToLog: uuid => dispatch<any>(navigateToLogCollection(uuid)),
 +    loadInputs: containerRequest => dispatch<any>(loadInputs(containerRequest)),
 +    loadOutputs: containerRequest => dispatch<any>(loadOutputs(containerRequest)),
 +    loadOutputDefinitions: containerRequest => dispatch<any>(loadOutputDefinitions(containerRequest)),
      updateOutputParams: () => dispatch<any>(updateOutputParams()),
 -    loadNodeJson: (containerRequest) => dispatch<any>(loadNodeJson(containerRequest)),
 -    pollProcessLogs: (processUuid) => dispatch<any>(pollProcessLogs(processUuid)),
 +    loadNodeJson: containerRequest => dispatch<any>(loadNodeJson(containerRequest)),
++    pollProcessLogs: processUuid => dispatch<any>(pollProcessLogs(processUuid)),
  });
  
  const getFilters = (processPanel: ProcessPanelState, processes: Process[]) => {
diff --cc src/views/workbench/workbench.tsx
index f7b8e833,ce930746..be254251
--- a/src/views/workbench/workbench.tsx
+++ b/src/views/workbench/workbench.tsx
@@@ -2,108 -2,112 +2,112 @@@
  //
  // SPDX-License-Identifier: AGPL-3.0
  
 -import React from 'react';
 -import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles';
 +import React from "react";
 +import { StyleRulesCallback, WithStyles, withStyles } from "@material-ui/core/styles";
  import { Route, Switch } from "react-router";
  import { ProjectPanel } from "views/project-panel/project-panel";
 -import { DetailsPanel } from 'views-components/details-panel/details-panel';
 -import { ArvadosTheme } from 'common/custom-theme';
 +import { DetailsPanel } from "views-components/details-panel/details-panel";
 +import { ArvadosTheme } from "common/custom-theme";
  import { ContextMenu } from "views-components/context-menu/context-menu";
  import { FavoritePanel } from "../favorite-panel/favorite-panel";
 -import { TokenDialog } from 'views-components/token-dialog/token-dialog';
 -import { RichTextEditorDialog } from 'views-components/rich-text-editor-dialog/rich-text-editor-dialog';
 -import { Snackbar } from 'views-components/snackbar/snackbar';
 -import { CollectionPanel } from '../collection-panel/collection-panel';
 -import { RenameFileDialog } from 'views-components/rename-file-dialog/rename-file-dialog';
 -import { FileRemoveDialog } from 'views-components/file-remove-dialog/file-remove-dialog';
 -import { MultipleFilesRemoveDialog } from 'views-components/file-remove-dialog/multiple-files-remove-dialog';
 -import { Routes } from 'routes/routes';
 -import { SidePanel } from 'views-components/side-panel/side-panel';
 -import { ProcessPanel } from 'views/process-panel/process-panel';
 -import { ChangeWorkflowDialog } from 'views-components/run-process-dialog/change-workflow-dialog';
 -import { CreateProjectDialog } from 'views-components/dialog-forms/create-project-dialog';
 -import { CreateCollectionDialog } from 'views-components/dialog-forms/create-collection-dialog';
 -import { CopyCollectionDialog } from 'views-components/dialog-forms/copy-collection-dialog';
 -import { CopyProcessDialog } from 'views-components/dialog-forms/copy-process-dialog';
 -import { UpdateCollectionDialog } from 'views-components/dialog-forms/update-collection-dialog';
 -import { UpdateProcessDialog } from 'views-components/dialog-forms/update-process-dialog';
 -import { UpdateProjectDialog } from 'views-components/dialog-forms/update-project-dialog';
 -import { MoveProcessDialog } from 'views-components/dialog-forms/move-process-dialog';
 -import { MoveProjectDialog } from 'views-components/dialog-forms/move-project-dialog';
 -import { MoveCollectionDialog } from 'views-components/dialog-forms/move-collection-dialog';
 -import { FilesUploadCollectionDialog } from 'views-components/dialog-forms/files-upload-collection-dialog';
 -import { PartialCopyToNewCollectionDialog } from 'views-components/dialog-forms/partial-copy-to-new-collection-dialog';
 -import { PartialCopyToExistingCollectionDialog } from 'views-components/dialog-forms/partial-copy-to-existing-collection-dialog';
 -import { PartialCopyToSeparateCollectionsDialog } from 'views-components/dialog-forms/partial-copy-to-separate-collections-dialog';
 -import { PartialMoveToNewCollectionDialog } from 'views-components/dialog-forms/partial-move-to-new-collection-dialog';
 -import { PartialMoveToExistingCollectionDialog } from 'views-components/dialog-forms/partial-move-to-existing-collection-dialog';
 -import { PartialMoveToSeparateCollectionsDialog } from 'views-components/dialog-forms/partial-move-to-separate-collections-dialog';
 -import { RemoveProcessDialog } from 'views-components/process-remove-dialog/process-remove-dialog';
 -import { MainContentBar } from 'views-components/main-content-bar/main-content-bar';
 -import { Grid } from '@material-ui/core';
 +import { TokenDialog } from "views-components/token-dialog/token-dialog";
 +import { RichTextEditorDialog } from "views-components/rich-text-editor-dialog/rich-text-editor-dialog";
 +import { Snackbar } from "views-components/snackbar/snackbar";
 +import { CollectionPanel } from "../collection-panel/collection-panel";
 +import { RenameFileDialog } from "views-components/rename-file-dialog/rename-file-dialog";
 +import { FileRemoveDialog } from "views-components/file-remove-dialog/file-remove-dialog";
 +import { MultipleFilesRemoveDialog } from "views-components/file-remove-dialog/multiple-files-remove-dialog";
 +import { Routes } from "routes/routes";
 +import { SidePanel } from "views-components/side-panel/side-panel";
 +import { ProcessPanel } from "views/process-panel/process-panel";
 +import { ChangeWorkflowDialog } from "views-components/run-process-dialog/change-workflow-dialog";
 +import { CreateProjectDialog } from "views-components/dialog-forms/create-project-dialog";
 +import { CreateCollectionDialog } from "views-components/dialog-forms/create-collection-dialog";
 +import { CopyCollectionDialog, CopyMultiCollectionDialog } from "views-components/dialog-forms/copy-collection-dialog";
 +import { CopyProcessDialog } from "views-components/dialog-forms/copy-process-dialog";
 +import { UpdateCollectionDialog } from "views-components/dialog-forms/update-collection-dialog";
 +import { UpdateProcessDialog } from "views-components/dialog-forms/update-process-dialog";
 +import { UpdateProjectDialog } from "views-components/dialog-forms/update-project-dialog";
 +import { MoveProcessDialog } from "views-components/dialog-forms/move-process-dialog";
 +import { MoveProjectDialog } from "views-components/dialog-forms/move-project-dialog";
 +import { MoveCollectionDialog } from "views-components/dialog-forms/move-collection-dialog";
 +import { FilesUploadCollectionDialog } from "views-components/dialog-forms/files-upload-collection-dialog";
- import { PartialCopyCollectionDialog } from "views-components/dialog-forms/partial-copy-collection-dialog";
++import { PartialCopyToNewCollectionDialog } from "views-components/dialog-forms/partial-copy-to-new-collection-dialog";
++import { PartialCopyToExistingCollectionDialog } from "views-components/dialog-forms/partial-copy-to-existing-collection-dialog";
++import { PartialCopyToSeparateCollectionsDialog } from "views-components/dialog-forms/partial-copy-to-separate-collections-dialog";
++import { PartialMoveToNewCollectionDialog } from "views-components/dialog-forms/partial-move-to-new-collection-dialog";
++import { PartialMoveToExistingCollectionDialog } from "views-components/dialog-forms/partial-move-to-existing-collection-dialog";
++import { PartialMoveToSeparateCollectionsDialog } from "views-components/dialog-forms/partial-move-to-separate-collections-dialog";
 +import { RemoveProcessDialog } from "views-components/process-remove-dialog/process-remove-dialog";
 +import { MainContentBar } from "views-components/main-content-bar/main-content-bar";
 +import { Grid } from "@material-ui/core";
  import { TrashPanel } from "views/trash-panel/trash-panel";
 -import { SharedWithMePanel } from 'views/shared-with-me-panel/shared-with-me-panel';
 -import { RunProcessPanel } from 'views/run-process-panel/run-process-panel';
 -import SplitterLayout from 'react-splitter-layout';
 -import { WorkflowPanel } from 'views/workflow-panel/workflow-panel';
 -import { RegisteredWorkflowPanel } from 'views/workflow-panel/registered-workflow-panel';
 -import { SearchResultsPanel } from 'views/search-results-panel/search-results-panel';
 -import { SshKeyPanel } from 'views/ssh-key-panel/ssh-key-panel';
 -import { SshKeyAdminPanel } from 'views/ssh-key-panel/ssh-key-admin-panel';
 +import { SharedWithMePanel } from "views/shared-with-me-panel/shared-with-me-panel";
 +import { RunProcessPanel } from "views/run-process-panel/run-process-panel";
 +import SplitterLayout from "react-splitter-layout";
 +import { WorkflowPanel } from "views/workflow-panel/workflow-panel";
 +import { RegisteredWorkflowPanel } from "views/workflow-panel/registered-workflow-panel";
 +import { SearchResultsPanel } from "views/search-results-panel/search-results-panel";
 +import { SshKeyPanel } from "views/ssh-key-panel/ssh-key-panel";
 +import { SshKeyAdminPanel } from "views/ssh-key-panel/ssh-key-admin-panel";
  import { SiteManagerPanel } from "views/site-manager-panel/site-manager-panel";
 -import { UserProfilePanel } from 'views/user-profile-panel/user-profile-panel';
 -import { SharingDialog } from 'views-components/sharing-dialog/sharing-dialog';
 -import { NotFoundDialog } from 'views-components/not-found-dialog/not-found-dialog';
 -import { AdvancedTabDialog } from 'views-components/advanced-tab-dialog/advanced-tab-dialog';
 -import { ProcessInputDialog } from 'views-components/process-input-dialog/process-input-dialog';
 -import { VirtualMachineUserPanel } from 'views/virtual-machine-panel/virtual-machine-user-panel';
 -import { VirtualMachineAdminPanel } from 'views/virtual-machine-panel/virtual-machine-admin-panel';
 -import { RepositoriesPanel } from 'views/repositories-panel/repositories-panel';
 -import { KeepServicePanel } from 'views/keep-service-panel/keep-service-panel';
 -import { ApiClientAuthorizationPanel } from 'views/api-client-authorization-panel/api-client-authorization-panel';
 -import { LinkPanel } from 'views/link-panel/link-panel';
 -import { RepositoriesSampleGitDialog } from 'views-components/repositories-sample-git-dialog/repositories-sample-git-dialog';
 -import { RepositoryAttributesDialog } from 'views-components/repository-attributes-dialog/repository-attributes-dialog';
 -import { CreateRepositoryDialog } from 'views-components/dialog-forms/create-repository-dialog';
 -import { RemoveRepositoryDialog } from 'views-components/repository-remove-dialog/repository-remove-dialog';
 -import { CreateSshKeyDialog } from 'views-components/dialog-forms/create-ssh-key-dialog';
 -import { PublicKeyDialog } from 'views-components/ssh-keys-dialog/public-key-dialog';
 -import { RemoveApiClientAuthorizationDialog } from 'views-components/api-client-authorizations-dialog/remove-dialog';
 -import { RemoveKeepServiceDialog } from 'views-components/keep-services-dialog/remove-dialog';
 -import { RemoveLinkDialog } from 'views-components/links-dialog/remove-dialog';
 -import { RemoveSshKeyDialog } from 'views-components/ssh-keys-dialog/remove-dialog';
 -import { VirtualMachineAttributesDialog } from 'views-components/virtual-machines-dialog/attributes-dialog';
 -import { RemoveVirtualMachineDialog } from 'views-components/virtual-machines-dialog/remove-dialog';
 -import { RemoveVirtualMachineLoginDialog } from 'views-components/virtual-machines-dialog/remove-login-dialog';
 -import { VirtualMachineAddLoginDialog } from 'views-components/virtual-machines-dialog/add-login-dialog';
 -import { AttributesApiClientAuthorizationDialog } from 'views-components/api-client-authorizations-dialog/attributes-dialog';
 -import { AttributesKeepServiceDialog } from 'views-components/keep-services-dialog/attributes-dialog';
 -import { AttributesLinkDialog } from 'views-components/links-dialog/attributes-dialog';
 -import { AttributesSshKeyDialog } from 'views-components/ssh-keys-dialog/attributes-dialog';
 -import { UserPanel } from 'views/user-panel/user-panel';
 -import { UserAttributesDialog } from 'views-components/user-dialog/attributes-dialog';
 -import { CreateUserDialog } from 'views-components/dialog-forms/create-user-dialog';
 -import { HelpApiClientAuthorizationDialog } from 'views-components/api-client-authorizations-dialog/help-dialog';
 -import { DeactivateDialog } from 'views-components/user-dialog/deactivate-dialog';
 -import { ActivateDialog } from 'views-components/user-dialog/activate-dialog';
 -import { SetupDialog } from 'views-components/user-dialog/setup-dialog';
 -import { GroupsPanel } from 'views/groups-panel/groups-panel';
 -import { RemoveGroupDialog } from 'views-components/groups-dialog/remove-dialog';
 -import { GroupAttributesDialog } from 'views-components/groups-dialog/attributes-dialog';
 -import { GroupDetailsPanel } from 'views/group-details-panel/group-details-panel';
 -import { RemoveGroupMemberDialog } from 'views-components/groups-dialog/member-remove-dialog';
 -import { GroupMemberAttributesDialog } from 'views-components/groups-dialog/member-attributes-dialog';
 -import { PublicFavoritePanel } from 'views/public-favorites-panel/public-favorites-panel';
 -import { LinkAccountPanel } from 'views/link-account-panel/link-account-panel';
 -import { FedLogin } from './fed-login';
 -import { CollectionsContentAddressPanel } from 'views/collection-content-address-panel/collection-content-address-panel';
 -import { AllProcessesPanel } from '../all-processes-panel/all-processes-panel';
 -import { NotFoundPanel } from '../not-found-panel/not-found-panel';
 -import { AutoLogout } from 'views-components/auto-logout/auto-logout';
 -import { RestoreCollectionVersionDialog } from 'views-components/collections-dialog/restore-version-dialog';
 -import { WebDavS3InfoDialog } from 'views-components/webdav-s3-dialog/webdav-s3-dialog';
 -import { pluginConfig } from 'plugins';
 -import { ElementListReducer } from 'common/plugintypes';
 -import { COLLAPSE_ICON_SIZE } from 'views-components/side-panel-toggle/side-panel-toggle'
 -import { Banner } from 'views-components/baner/banner';
 +import { UserProfilePanel } from "views/user-profile-panel/user-profile-panel";
 +import { SharingDialog } from "views-components/sharing-dialog/sharing-dialog";
 +import { NotFoundDialog } from "views-components/not-found-dialog/not-found-dialog";
 +import { AdvancedTabDialog } from "views-components/advanced-tab-dialog/advanced-tab-dialog";
 +import { ProcessInputDialog } from "views-components/process-input-dialog/process-input-dialog";
 +import { VirtualMachineUserPanel } from "views/virtual-machine-panel/virtual-machine-user-panel";
 +import { VirtualMachineAdminPanel } from "views/virtual-machine-panel/virtual-machine-admin-panel";
 +import { RepositoriesPanel } from "views/repositories-panel/repositories-panel";
 +import { KeepServicePanel } from "views/keep-service-panel/keep-service-panel";
 +import { ApiClientAuthorizationPanel } from "views/api-client-authorization-panel/api-client-authorization-panel";
 +import { LinkPanel } from "views/link-panel/link-panel";
 +import { RepositoriesSampleGitDialog } from "views-components/repositories-sample-git-dialog/repositories-sample-git-dialog";
 +import { RepositoryAttributesDialog } from "views-components/repository-attributes-dialog/repository-attributes-dialog";
 +import { CreateRepositoryDialog } from "views-components/dialog-forms/create-repository-dialog";
 +import { RemoveRepositoryDialog } from "views-components/repository-remove-dialog/repository-remove-dialog";
 +import { CreateSshKeyDialog } from "views-components/dialog-forms/create-ssh-key-dialog";
 +import { PublicKeyDialog } from "views-components/ssh-keys-dialog/public-key-dialog";
 +import { RemoveApiClientAuthorizationDialog } from "views-components/api-client-authorizations-dialog/remove-dialog";
 +import { RemoveKeepServiceDialog } from "views-components/keep-services-dialog/remove-dialog";
 +import { RemoveLinkDialog } from "views-components/links-dialog/remove-dialog";
 +import { RemoveSshKeyDialog } from "views-components/ssh-keys-dialog/remove-dialog";
 +import { VirtualMachineAttributesDialog } from "views-components/virtual-machines-dialog/attributes-dialog";
 +import { RemoveVirtualMachineDialog } from "views-components/virtual-machines-dialog/remove-dialog";
 +import { RemoveVirtualMachineLoginDialog } from "views-components/virtual-machines-dialog/remove-login-dialog";
 +import { VirtualMachineAddLoginDialog } from "views-components/virtual-machines-dialog/add-login-dialog";
 +import { AttributesApiClientAuthorizationDialog } from "views-components/api-client-authorizations-dialog/attributes-dialog";
 +import { AttributesKeepServiceDialog } from "views-components/keep-services-dialog/attributes-dialog";
 +import { AttributesLinkDialog } from "views-components/links-dialog/attributes-dialog";
 +import { AttributesSshKeyDialog } from "views-components/ssh-keys-dialog/attributes-dialog";
 +import { UserPanel } from "views/user-panel/user-panel";
 +import { UserAttributesDialog } from "views-components/user-dialog/attributes-dialog";
 +import { CreateUserDialog } from "views-components/dialog-forms/create-user-dialog";
 +import { HelpApiClientAuthorizationDialog } from "views-components/api-client-authorizations-dialog/help-dialog";
 +import { DeactivateDialog } from "views-components/user-dialog/deactivate-dialog";
 +import { ActivateDialog } from "views-components/user-dialog/activate-dialog";
 +import { SetupDialog } from "views-components/user-dialog/setup-dialog";
 +import { GroupsPanel } from "views/groups-panel/groups-panel";
 +import { RemoveGroupDialog } from "views-components/groups-dialog/remove-dialog";
 +import { GroupAttributesDialog } from "views-components/groups-dialog/attributes-dialog";
 +import { GroupDetailsPanel } from "views/group-details-panel/group-details-panel";
 +import { RemoveGroupMemberDialog } from "views-components/groups-dialog/member-remove-dialog";
 +import { GroupMemberAttributesDialog } from "views-components/groups-dialog/member-attributes-dialog";
- import { PartialCopyToCollectionDialog } from "views-components/dialog-forms/partial-copy-to-collection-dialog";
 +import { PublicFavoritePanel } from "views/public-favorites-panel/public-favorites-panel";
 +import { LinkAccountPanel } from "views/link-account-panel/link-account-panel";
 +import { FedLogin } from "./fed-login";
 +import { CollectionsContentAddressPanel } from "views/collection-content-address-panel/collection-content-address-panel";
 +import { AllProcessesPanel } from "../all-processes-panel/all-processes-panel";
 +import { NotFoundPanel } from "../not-found-panel/not-found-panel";
 +import { AutoLogout } from "views-components/auto-logout/auto-logout";
 +import { RestoreCollectionVersionDialog } from "views-components/collections-dialog/restore-version-dialog";
 +import { WebDavS3InfoDialog } from "views-components/webdav-s3-dialog/webdav-s3-dialog";
 +import { pluginConfig } from "plugins";
 +import { ElementListReducer } from "common/plugintypes";
 +import { COLLAPSE_ICON_SIZE } from "views-components/side-panel-toggle/side-panel-toggle";
 +import { Banner } from "views-components/baner/banner";
  
 -type CssRules = 'root' | 'container' | 'splitter' | 'asidePanel' | 'contentWrapper' | 'content';
 +type CssRules = "root" | "container" | "splitter" | "asidePanel" | "contentWrapper" | "content";
  
  const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
      root: {
@@@ -156,202 -160,81 +160,209 @@@ const getSplitterInitialSize = () => 
      return splitterSize ? Number(splitterSize) : defaultSplitterSize;
  };
  
 -const saveSplitterSize = (size: number) => localStorage.setItem('splitterSize', size.toString());
 +const saveSplitterSize = (size: number) => localStorage.setItem("splitterSize", size.toString());
  
 -let routes = <>
 -    <Route path={Routes.PROJECTS} component={ProjectPanel} />
 -    <Route path={Routes.COLLECTIONS} component={CollectionPanel} />
 -    <Route path={Routes.FAVORITES} component={FavoritePanel} />
 -    <Route path={Routes.ALL_PROCESSES} component={AllProcessesPanel} />
 -    <Route path={Routes.PROCESSES} component={ProcessPanel} />
 -    <Route path={Routes.TRASH} component={TrashPanel} />
 -    <Route path={Routes.SHARED_WITH_ME} component={SharedWithMePanel} />
 -    <Route path={Routes.RUN_PROCESS} component={RunProcessPanel} />
 -    <Route path={Routes.REGISTEREDWORKFLOW} component={RegisteredWorkflowPanel} />
 -    <Route path={Routes.WORKFLOWS} component={WorkflowPanel} />
 -    <Route path={Routes.SEARCH_RESULTS} component={SearchResultsPanel} />
 -    <Route path={Routes.VIRTUAL_MACHINES_USER} component={VirtualMachineUserPanel} />
 -    <Route path={Routes.VIRTUAL_MACHINES_ADMIN} component={VirtualMachineAdminPanel} />
 -    <Route path={Routes.REPOSITORIES} component={RepositoriesPanel} />
 -    <Route path={Routes.SSH_KEYS_USER} component={SshKeyPanel} />
 -    <Route path={Routes.SSH_KEYS_ADMIN} component={SshKeyAdminPanel} />
 -    <Route path={Routes.SITE_MANAGER} component={SiteManagerPanel} />
 -    <Route path={Routes.KEEP_SERVICES} component={KeepServicePanel} />
 -    <Route path={Routes.USERS} component={UserPanel} />
 -    <Route path={Routes.API_CLIENT_AUTHORIZATIONS} component={ApiClientAuthorizationPanel} />
 -    <Route path={Routes.MY_ACCOUNT} component={UserProfilePanel} />
 -    <Route path={Routes.USER_PROFILE} component={UserProfilePanel} />
 -    <Route path={Routes.GROUPS} component={GroupsPanel} />
 -    <Route path={Routes.GROUP_DETAILS} component={GroupDetailsPanel} />
 -    <Route path={Routes.LINKS} component={LinkPanel} />
 -    <Route path={Routes.PUBLIC_FAVORITES} component={PublicFavoritePanel} />
 -    <Route path={Routes.LINK_ACCOUNT} component={LinkAccountPanel} />
 -    <Route path={Routes.COLLECTIONS_CONTENT_ADDRESS} component={CollectionsContentAddressPanel} />
 -</>;
 +let routes = (
 +    <>
 +        <Route
 +            path={Routes.PROJECTS}
 +            component={ProjectPanel}
 +        />
 +        <Route
 +            path={Routes.COLLECTIONS}
 +            component={CollectionPanel}
 +        />
 +        <Route
 +            path={Routes.FAVORITES}
 +            component={FavoritePanel}
 +        />
 +        <Route
 +            path={Routes.ALL_PROCESSES}
 +            component={AllProcessesPanel}
 +        />
 +        <Route
 +            path={Routes.PROCESSES}
 +            component={ProcessPanel}
 +        />
 +        <Route
 +            path={Routes.TRASH}
 +            component={TrashPanel}
 +        />
 +        <Route
 +            path={Routes.SHARED_WITH_ME}
 +            component={SharedWithMePanel}
 +        />
 +        <Route
 +            path={Routes.RUN_PROCESS}
 +            component={RunProcessPanel}
 +        />
 +        <Route
 +            path={Routes.REGISTEREDWORKFLOW}
 +            component={RegisteredWorkflowPanel}
 +        />
 +        <Route
 +            path={Routes.WORKFLOWS}
 +            component={WorkflowPanel}
 +        />
 +        <Route
 +            path={Routes.SEARCH_RESULTS}
 +            component={SearchResultsPanel}
 +        />
 +        <Route
 +            path={Routes.VIRTUAL_MACHINES_USER}
 +            component={VirtualMachineUserPanel}
 +        />
 +        <Route
 +            path={Routes.VIRTUAL_MACHINES_ADMIN}
 +            component={VirtualMachineAdminPanel}
 +        />
 +        <Route
 +            path={Routes.REPOSITORIES}
 +            component={RepositoriesPanel}
 +        />
 +        <Route
 +            path={Routes.SSH_KEYS_USER}
 +            component={SshKeyPanel}
 +        />
 +        <Route
 +            path={Routes.SSH_KEYS_ADMIN}
 +            component={SshKeyAdminPanel}
 +        />
 +        <Route
 +            path={Routes.SITE_MANAGER}
 +            component={SiteManagerPanel}
 +        />
 +        <Route
 +            path={Routes.KEEP_SERVICES}
 +            component={KeepServicePanel}
 +        />
 +        <Route
 +            path={Routes.USERS}
 +            component={UserPanel}
 +        />
 +        <Route
 +            path={Routes.API_CLIENT_AUTHORIZATIONS}
 +            component={ApiClientAuthorizationPanel}
 +        />
 +        <Route
 +            path={Routes.MY_ACCOUNT}
 +            component={UserProfilePanel}
 +        />
 +        <Route
 +            path={Routes.USER_PROFILE}
 +            component={UserProfilePanel}
 +        />
 +        <Route
 +            path={Routes.GROUPS}
 +            component={GroupsPanel}
 +        />
 +        <Route
 +            path={Routes.GROUP_DETAILS}
 +            component={GroupDetailsPanel}
 +        />
 +        <Route
 +            path={Routes.LINKS}
 +            component={LinkPanel}
 +        />
 +        <Route
 +            path={Routes.PUBLIC_FAVORITES}
 +            component={PublicFavoritePanel}
 +        />
 +        <Route
 +            path={Routes.LINK_ACCOUNT}
 +            component={LinkAccountPanel}
 +        />
 +        <Route
 +            path={Routes.COLLECTIONS_CONTENT_ADDRESS}
 +            component={CollectionsContentAddressPanel}
 +        />
 +    </>
 +);
  
 -const reduceRoutesFn: (a: React.ReactElement[],
 -    b: ElementListReducer) => React.ReactElement[] = (a, b) => b(a);
 +const reduceRoutesFn: (a: React.ReactElement[], b: ElementListReducer) => React.ReactElement[] = (a, b) => b(a);
  
 -routes = React.createElement(React.Fragment, null, pluginConfig.centerPanelList.reduce(reduceRoutesFn, React.Children.toArray(routes.props.children)));
 +routes = React.createElement(
 +    React.Fragment,
 +    null,
 +    pluginConfig.centerPanelList.reduce(reduceRoutesFn, React.Children.toArray(routes.props.children))
 +);
  
 -const applyCollapsedState = (isCollapsed) => {
 -    const rightPanel: Element = document.getElementsByClassName('layout-pane')[1]
 -    const totalWidth: number = document.getElementsByClassName('splitter-layout')[0]?.clientWidth
 -    const rightPanelExpandedWidth = ((totalWidth - COLLAPSE_ICON_SIZE)) / (totalWidth / 100)
 +const applyCollapsedState = isCollapsed => {
 +    const rightPanel: Element = document.getElementsByClassName("layout-pane")[1];
 +    const totalWidth: number = document.getElementsByClassName("splitter-layout")[0]?.clientWidth;
 +    const rightPanelExpandedWidth = (totalWidth - COLLAPSE_ICON_SIZE) / (totalWidth / 100);
      if (rightPanel) {
 -        rightPanel.setAttribute('style', `width: ${isCollapsed ? rightPanelExpandedWidth : getSplitterInitialSize()}%`)
 +        rightPanel.setAttribute("style", `width: ${isCollapsed ? rightPanelExpandedWidth : getSplitterInitialSize()}%`);
      }
 -    const splitter = document.getElementsByClassName('layout-splitter')[0]
 -    isCollapsed ? splitter?.classList.add('layout-splitter-disabled') : splitter?.classList.remove('layout-splitter-disabled')
 -
 -}
 -
 -export const WorkbenchPanel =
 -    withStyles(styles)((props: WorkbenchPanelProps) => {
 +    const splitter = document.getElementsByClassName("layout-splitter")[0];
 +    isCollapsed ? splitter?.classList.add("layout-splitter-disabled") : splitter?.classList.remove("layout-splitter-disabled");
 +};
  
 -        //panel size will not scale automatically on window resize, so we do it manually
 -        window.addEventListener('resize', () => applyCollapsedState(props.sidePanelIsCollapsed))
 -        applyCollapsedState(props.sidePanelIsCollapsed)
 +export const WorkbenchPanel = withStyles(styles)((props: WorkbenchPanelProps) => {
 +    //panel size will not scale automatically on window resize, so we do it manually
 +    window.addEventListener("resize", () => applyCollapsedState(props.sidePanelIsCollapsed));
 +    applyCollapsedState(props.sidePanelIsCollapsed);
  
 -        return <Grid container item xs className={props.classes.root}>
 +    return (
 +        <Grid
 +            container
 +            item
 +            xs
-             className={props.classes.root}>
++            className={props.classes.root}
++        >
              {props.sessionIdleTimeout > 0 && <AutoLogout />}
 -            <Grid container item xs className={props.classes.container}>
 -                <SplitterLayout customClassName={props.classes.splitter} percentage={true}
 -                    primaryIndex={0} primaryMinSize={10}
 -                    secondaryInitialSize={getSplitterInitialSize()} secondaryMinSize={40}
 -                    onSecondaryPaneSizeChange={saveSplitterSize}>
 -                    {props.isUserActive && props.isNotLinking && <Grid container item xs component='aside' direction='column' className={props.classes.asidePanel}>
 -                        <SidePanel />
 -                    </Grid>}
 -                    <Grid container item xs component="main" direction="column" className={props.classes.contentWrapper}>
 -                        <Grid item xs>
 +            <Grid
 +                container
 +                item
 +                xs
-                 className={props.classes.container}>
++                className={props.classes.container}
++            >
 +                <SplitterLayout
 +                    customClassName={props.classes.splitter}
 +                    percentage={true}
 +                    primaryIndex={0}
 +                    primaryMinSize={10}
 +                    secondaryInitialSize={getSplitterInitialSize()}
 +                    secondaryMinSize={40}
-                     onSecondaryPaneSizeChange={saveSplitterSize}>
++                    onSecondaryPaneSizeChange={saveSplitterSize}
++                >
 +                    {props.isUserActive && props.isNotLinking && (
 +                        <Grid
 +                            container
 +                            item
 +                            xs
 +                            component="aside"
 +                            direction="column"
-                             className={props.classes.asidePanel}>
++                            className={props.classes.asidePanel}
++                        >
 +                            <SidePanel />
 +                        </Grid>
 +                    )}
 +                    <Grid
 +                        container
 +                        item
 +                        xs
 +                        component="main"
 +                        direction="column"
-                         className={props.classes.contentWrapper}>
++                        className={props.classes.contentWrapper}
++                    >
 +                        <Grid
 +                            item
-                             xs>
++                            xs
++                        >
                              {props.isNotLinking && <MainContentBar />}
                          </Grid>
 -                        <Grid item xs className={props.classes.content}>
 +                        <Grid
 +                            item
 +                            xs
-                             className={props.classes.content}>
++                            className={props.classes.content}
++                        >
                              <Switch>
                                  {routes.props.children}
 -                                <Route path={Routes.NO_MATCH} component={NotFoundPanel} />
 +                                <Route
 +                                    path={Routes.NO_MATCH}
 +                                    component={NotFoundPanel}
 +                                />
                              </Switch>
                          </Grid>
                      </Grid>

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list