[ARVADOS-WORKBENCH2] updated: 1.4.1-368-g7cd56c2b

Git user git at public.arvados.org
Mon Jun 15 21:51:47 UTC 2020


Summary of changes:
 cypress/integration/side-panel.spec.js             | 142 +++++++++++++++++++++
 src/common/test/group-fixtures.ts                  |  13 ++
 src/components/breadcrumbs/breadcrumbs.tsx         |   8 +-
 src/components/form-dialog/form-dialog.tsx         |   3 +-
 src/store/resources/resources.test.ts              |  55 ++++----
 src/store/resources/resources.ts                   |   2 +-
 .../form-fields/collection-form-fields.tsx         |   4 +-
 .../form-fields/project-form-fields.tsx            |   4 +-
 .../form-fields/resource-form-fields.tsx           |   4 +-
 .../side-panel-button/side-panel-button.tsx        |   8 +-
 src/views/favorite-panel/favorite-panel.tsx        |  14 +-
 11 files changed, 214 insertions(+), 43 deletions(-)
 create mode 100644 cypress/integration/side-panel.spec.js
 create mode 100644 src/common/test/group-fixtures.ts

       via  7cd56c2b3cb65089a6268d5a3de24466d1bb8a24 (commit)
       via  e4d129611fe6191b11e99486b47a53a00ef61278 (commit)
       via  0571951f6c77ec7b73eba240c117a05a50fe6ca0 (commit)
       via  3f4b4e61c725b05f6b4ec1ddd2ef1826274fc0e0 (commit)
       via  efcea950b84b26c4cbbdf4df8fd5c88f6c846da9 (commit)
       via  662c6eecb507aa8dc4d253630c82aa06a57b0162 (commit)
       via  fcbcbbb978fbb0d381c23a2ac204940c1c80dda3 (commit)
       via  6ad3586e61737306f61a330eca545ca494f16304 (commit)
      from  ea0b48ab40307283dc9c7460cc8196040b669491 (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 7cd56c2b3cb65089a6268d5a3de24466d1bb8a24
Merge: e4d12961 ea0b48ab
Author: Daniel Kutyła <daniel.kutyla at contractors.roche.com>
Date:   Mon Jun 15 23:50:23 2020 +0200

    16437: Branch sync
    
    Arvados-DCO-1.1-Signed-off-by: Daniel Kutyła <daniel.kutyla at contractors.roche.com>

diff --cc src/store/resources/resources.test.ts
index 111ce6fc,a4c12039..1dfeaf96
--- a/src/store/resources/resources.test.ts
+++ b/src/store/resources/resources.test.ts
@@@ -4,44 -4,25 +4,26 @@@
  
  import { getResourceWithEditableStatus } from "./resources";
  import { ResourceKind } from "~/models/resource";
- <<<<<<< HEAD
 +import groupFixtures from "../../common/test/group-fixtures";
- =======
- >>>>>>> ea0b48ab... 16437: Removes context items when projects are not editable by user
  
  describe('resources', () => {
      describe('getResourceWithEditableStatus', () => {
          const resourcesState = {
- <<<<<<< HEAD
 -            'zzzzz-j7d0g-0123456789ab123': {
 -                uuid: 'zzzzz-j7d0g-0123456789ab123',
 -                ownerUuid: 'zzzzz-tpzed-0123456789ab789',
 +            [groupFixtures.editable_project_resource_uuid]: {
 +                uuid: groupFixtures.editable_project_resource_uuid,
 +                ownerUuid: groupFixtures.user_uuid,
- =======
-             'zzzzz-j7d0g-0123456789ab123': {
-                 uuid: 'zzzzz-j7d0g-0123456789ab123',
-                 ownerUuid: 'zzzzz-tpzed-0123456789ab789',
- >>>>>>> ea0b48ab... 16437: Removes context items when projects are not editable by user
                  createdAt: 'string',
                  modifiedByClientUuid: 'string',
                  modifiedByUserUuid: 'string',
                  modifiedAt: 'string',
                  href: 'string',
                  kind: ResourceKind.PROJECT,
- <<<<<<< HEAD
 -                writableBy: ['zzzzz-tpzed-0123456789ab789'],
 +                writableBy: [groupFixtures.user_uuid],
                  etag: 'string',
              },
 -            'zzzzz-4zz18-0123456789ab456': {
 -                uuid: 'zzzzz-4zz18-0123456789ab456',
 -                ownerUuid: 'zzzzz-j7d0g-0123456789ab123',
 +            [groupFixtures.editable_collection_resource_uuid]: {
 +                uuid: groupFixtures.editable_collection_resource_uuid,
 +                ownerUuid: groupFixtures.editable_project_resource_uuid,
- =======
-                 writableBy: ['zzzzz-tpzed-0123456789ab789'],
-                 etag: 'string',
-             },
-             'zzzzz-4zz18-0123456789ab456': {
-                 uuid: 'zzzzz-4zz18-0123456789ab456',
-                 ownerUuid: 'zzzzz-j7d0g-0123456789ab123',
- >>>>>>> ea0b48ab... 16437: Removes context items when projects are not editable by user
                  createdAt: 'string',
                  modifiedByClientUuid: 'string',
                  modifiedByUserUuid: 'string',
@@@ -50,36 -31,21 +32,21 @@@
                  kind: ResourceKind.COLLECTION,
                  etag: 'string',
              },
- <<<<<<< HEAD
 -            'zzzzz-j7d0g-0123456789ab321': {
 -                uuid: 'zzzzz-j7d0g-0123456789ab321',
 -                ownerUuid: 'zzzzz-tpzed-0123456789ab987',
 +            [groupFixtures.not_editable_project_resource_uuid]: {
 +                uuid: groupFixtures.not_editable_project_resource_uuid,
 +                ownerUuid: groupFixtures.unknown_user_resource_uuid,
- =======
-             'zzzzz-j7d0g-0123456789ab321': {
-                 uuid: 'zzzzz-j7d0g-0123456789ab321',
-                 ownerUuid: 'zzzzz-tpzed-0123456789ab987',
- >>>>>>> ea0b48ab... 16437: Removes context items when projects are not editable by user
                  createdAt: 'string',
                  modifiedByClientUuid: 'string',
                  modifiedByUserUuid: 'string',
                  modifiedAt: 'string',
                  href: 'string',
                  kind: ResourceKind.PROJECT,
- <<<<<<< HEAD
 -                writableBy: ['zzzzz-tpzed-0123456789ab987'],
 +                writableBy: [groupFixtures.unknown_user_resource_uuid],
                  etag: 'string',
              },
 -            'zzzzz-4zz18-0123456789ab654': {
 -                uuid: 'zzzzz-4zz18-0123456789ab654',
 -                ownerUuid: 'zzzzz-j7d0g-0123456789ab321',
 +            [groupFixtures.not_editable_collection_resource_uuid]: {
 +                uuid: groupFixtures.not_editable_collection_resource_uuid,
 +                ownerUuid: groupFixtures.not_editable_project_resource_uuid,
- =======
-                 writableBy: ['zzzzz-tpzed-0123456789ab987'],
-                 etag: 'string',
-             },
-             'zzzzz-4zz18-0123456789ab654': {
-                 uuid: 'zzzzz-4zz18-0123456789ab654',
-                 ownerUuid: 'zzzzz-j7d0g-0123456789ab321',
- >>>>>>> ea0b48ab... 16437: Removes context items when projects are not editable by user
                  createdAt: 'string',
                  modifiedByClientUuid: 'string',
                  modifiedByUserUuid: 'string',
@@@ -88,15 -54,9 +55,9 @@@
                  kind: ResourceKind.COLLECTION,
                  etag: 'string',
              },
- <<<<<<< HEAD
 -            'zzzzz-tpzed-0123456789abcde': {
 -                uuid: 'zzzzz-tpzed-0123456789abcde',
 -                ownerUuid: 'zzzzz-tpzed-0123456789abcde',
 +            [groupFixtures.user_resource_uuid]: {
 +                uuid: groupFixtures.user_resource_uuid,
 +                ownerUuid: groupFixtures.user_resource_uuid,
- =======
-             'zzzzz-tpzed-0123456789abcde': {
-                 uuid: 'zzzzz-tpzed-0123456789abcde',
-                 ownerUuid: 'zzzzz-tpzed-0123456789abcde',
- >>>>>>> ea0b48ab... 16437: Removes context items when projects are not editable by user
                  createdAt: 'string',
                  modifiedByClientUuid: 'string',
                  modifiedByUserUuid: 'string',
@@@ -109,13 -69,8 +70,8 @@@
  
          it('should return editable user resource (resource UUID is equal to user UUID)', () => {
              // given
- <<<<<<< HEAD
 -            const id = 'zzzzz-tpzed-0123456789abcde';
 -            const userUuid = 'zzzzz-tpzed-0123456789abcde';
 +            const id = groupFixtures.user_resource_uuid;
 +            const userUuid = groupFixtures.user_resource_uuid;
- =======
-             const id = 'zzzzz-tpzed-0123456789abcde';
-             const userUuid = 'zzzzz-tpzed-0123456789abcde';
- >>>>>>> ea0b48ab... 16437: Removes context items when projects are not editable by user
  
              // when
              const result = getResourceWithEditableStatus(id, userUuid)(resourcesState);
@@@ -126,13 -81,8 +82,8 @@@
  
          it('should return editable project resource', () => {
              // given
- <<<<<<< HEAD
 -            const id = 'zzzzz-j7d0g-0123456789ab123';
 -            const userUuid = 'zzzzz-tpzed-0123456789ab789';
 +            const id = groupFixtures.editable_project_resource_uuid;
 +            const userUuid = groupFixtures.user_uuid;
- =======
-             const id = 'zzzzz-j7d0g-0123456789ab123';
-             const userUuid = 'zzzzz-tpzed-0123456789ab789';
- >>>>>>> ea0b48ab... 16437: Removes context items when projects are not editable by user
  
              // when
              const result = getResourceWithEditableStatus(id, userUuid)(resourcesState);
@@@ -143,13 -93,8 +94,8 @@@
  
          it('should return editable collection resource', () => {
              // given
- <<<<<<< HEAD
 -            const id = 'zzzzz-4zz18-0123456789ab456';
 -            const userUuid = 'zzzzz-tpzed-0123456789ab789';
 +            const id = groupFixtures.editable_collection_resource_uuid;
 +            const userUuid = groupFixtures.user_uuid;
- =======
-             const id = 'zzzzz-4zz18-0123456789ab456';
-             const userUuid = 'zzzzz-tpzed-0123456789ab789';
- >>>>>>> ea0b48ab... 16437: Removes context items when projects are not editable by user
  
              // when
              const result = getResourceWithEditableStatus(id, userUuid)(resourcesState);
@@@ -160,13 -105,8 +106,8 @@@
  
          it('should return not editable project resource', () => {
              // given
- <<<<<<< HEAD
 -            const id = 'zzzzz-j7d0g-0123456789ab321';
 -            const userUuid = 'zzzzz-tpzed-0123456789ab789';
 +            const id = groupFixtures.not_editable_project_resource_uuid;
 +            const userUuid = groupFixtures.user_uuid;
- =======
-             const id = 'zzzzz-j7d0g-0123456789ab321';
-             const userUuid = 'zzzzz-tpzed-0123456789ab789';
- >>>>>>> ea0b48ab... 16437: Removes context items when projects are not editable by user
  
              // when
              const result = getResourceWithEditableStatus(id, userUuid)(resourcesState);
@@@ -177,13 -117,8 +118,8 @@@
  
          it('should return not editable collection resource', () => {
              // given
- <<<<<<< HEAD
 -            const id = 'zzzzz-4zz18-0123456789ab654';
 -            const userUuid = 'zzzzz-tpzed-0123456789ab789';
 +            const id = groupFixtures.not_editable_collection_resource_uuid;
 +            const userUuid = groupFixtures.user_uuid;
- =======
-             const id = 'zzzzz-4zz18-0123456789ab654';
-             const userUuid = 'zzzzz-tpzed-0123456789ab789';
- >>>>>>> ea0b48ab... 16437: Removes context items when projects are not editable by user
  
              // when
              const result = getResourceWithEditableStatus(id, userUuid)(resourcesState);

commit e4d129611fe6191b11e99486b47a53a00ef61278
Author: Daniel Kutyła <daniel.kutyla at contractors.roche.com>
Date:   Fri Jun 5 22:35:40 2020 +0200

    16437: Removes context items when projects are not editable by user
    
    Arvados-DCO-1.1-Signed-off-by: Daniel Kutyła <daniel.kutyla at contractors.roche.com>

diff --git a/src/store/resources/resources.test.ts b/src/store/resources/resources.test.ts
index 1dfeaf96..111ce6fc 100644
--- a/src/store/resources/resources.test.ts
+++ b/src/store/resources/resources.test.ts
@@ -4,26 +4,44 @@
 
 import { getResourceWithEditableStatus } from "./resources";
 import { ResourceKind } from "~/models/resource";
+<<<<<<< HEAD
 import groupFixtures from "../../common/test/group-fixtures";
+=======
+>>>>>>> ea0b48ab... 16437: Removes context items when projects are not editable by user
 
 describe('resources', () => {
     describe('getResourceWithEditableStatus', () => {
         const resourcesState = {
+<<<<<<< HEAD
             [groupFixtures.editable_project_resource_uuid]: {
                 uuid: groupFixtures.editable_project_resource_uuid,
                 ownerUuid: groupFixtures.user_uuid,
+=======
+            'zzzzz-j7d0g-0123456789ab123': {
+                uuid: 'zzzzz-j7d0g-0123456789ab123',
+                ownerUuid: 'zzzzz-tpzed-0123456789ab789',
+>>>>>>> ea0b48ab... 16437: Removes context items when projects are not editable by user
                 createdAt: 'string',
                 modifiedByClientUuid: 'string',
                 modifiedByUserUuid: 'string',
                 modifiedAt: 'string',
                 href: 'string',
                 kind: ResourceKind.PROJECT,
+<<<<<<< HEAD
                 writableBy: [groupFixtures.user_uuid],
                 etag: 'string',
             },
             [groupFixtures.editable_collection_resource_uuid]: {
                 uuid: groupFixtures.editable_collection_resource_uuid,
                 ownerUuid: groupFixtures.editable_project_resource_uuid,
+=======
+                writableBy: ['zzzzz-tpzed-0123456789ab789'],
+                etag: 'string',
+            },
+            'zzzzz-4zz18-0123456789ab456': {
+                uuid: 'zzzzz-4zz18-0123456789ab456',
+                ownerUuid: 'zzzzz-j7d0g-0123456789ab123',
+>>>>>>> ea0b48ab... 16437: Removes context items when projects are not editable by user
                 createdAt: 'string',
                 modifiedByClientUuid: 'string',
                 modifiedByUserUuid: 'string',
@@ -32,21 +50,36 @@ describe('resources', () => {
                 kind: ResourceKind.COLLECTION,
                 etag: 'string',
             },
+<<<<<<< HEAD
             [groupFixtures.not_editable_project_resource_uuid]: {
                 uuid: groupFixtures.not_editable_project_resource_uuid,
                 ownerUuid: groupFixtures.unknown_user_resource_uuid,
+=======
+            'zzzzz-j7d0g-0123456789ab321': {
+                uuid: 'zzzzz-j7d0g-0123456789ab321',
+                ownerUuid: 'zzzzz-tpzed-0123456789ab987',
+>>>>>>> ea0b48ab... 16437: Removes context items when projects are not editable by user
                 createdAt: 'string',
                 modifiedByClientUuid: 'string',
                 modifiedByUserUuid: 'string',
                 modifiedAt: 'string',
                 href: 'string',
                 kind: ResourceKind.PROJECT,
+<<<<<<< HEAD
                 writableBy: [groupFixtures.unknown_user_resource_uuid],
                 etag: 'string',
             },
             [groupFixtures.not_editable_collection_resource_uuid]: {
                 uuid: groupFixtures.not_editable_collection_resource_uuid,
                 ownerUuid: groupFixtures.not_editable_project_resource_uuid,
+=======
+                writableBy: ['zzzzz-tpzed-0123456789ab987'],
+                etag: 'string',
+            },
+            'zzzzz-4zz18-0123456789ab654': {
+                uuid: 'zzzzz-4zz18-0123456789ab654',
+                ownerUuid: 'zzzzz-j7d0g-0123456789ab321',
+>>>>>>> ea0b48ab... 16437: Removes context items when projects are not editable by user
                 createdAt: 'string',
                 modifiedByClientUuid: 'string',
                 modifiedByUserUuid: 'string',
@@ -55,9 +88,15 @@ describe('resources', () => {
                 kind: ResourceKind.COLLECTION,
                 etag: 'string',
             },
+<<<<<<< HEAD
             [groupFixtures.user_resource_uuid]: {
                 uuid: groupFixtures.user_resource_uuid,
                 ownerUuid: groupFixtures.user_resource_uuid,
+=======
+            'zzzzz-tpzed-0123456789abcde': {
+                uuid: 'zzzzz-tpzed-0123456789abcde',
+                ownerUuid: 'zzzzz-tpzed-0123456789abcde',
+>>>>>>> ea0b48ab... 16437: Removes context items when projects are not editable by user
                 createdAt: 'string',
                 modifiedByClientUuid: 'string',
                 modifiedByUserUuid: 'string',
@@ -70,8 +109,13 @@ describe('resources', () => {
 
         it('should return editable user resource (resource UUID is equal to user UUID)', () => {
             // given
+<<<<<<< HEAD
             const id = groupFixtures.user_resource_uuid;
             const userUuid = groupFixtures.user_resource_uuid;
+=======
+            const id = 'zzzzz-tpzed-0123456789abcde';
+            const userUuid = 'zzzzz-tpzed-0123456789abcde';
+>>>>>>> ea0b48ab... 16437: Removes context items when projects are not editable by user
 
             // when
             const result = getResourceWithEditableStatus(id, userUuid)(resourcesState);
@@ -82,8 +126,13 @@ describe('resources', () => {
 
         it('should return editable project resource', () => {
             // given
+<<<<<<< HEAD
             const id = groupFixtures.editable_project_resource_uuid;
             const userUuid = groupFixtures.user_uuid;
+=======
+            const id = 'zzzzz-j7d0g-0123456789ab123';
+            const userUuid = 'zzzzz-tpzed-0123456789ab789';
+>>>>>>> ea0b48ab... 16437: Removes context items when projects are not editable by user
 
             // when
             const result = getResourceWithEditableStatus(id, userUuid)(resourcesState);
@@ -94,8 +143,13 @@ describe('resources', () => {
 
         it('should return editable collection resource', () => {
             // given
+<<<<<<< HEAD
             const id = groupFixtures.editable_collection_resource_uuid;
             const userUuid = groupFixtures.user_uuid;
+=======
+            const id = 'zzzzz-4zz18-0123456789ab456';
+            const userUuid = 'zzzzz-tpzed-0123456789ab789';
+>>>>>>> ea0b48ab... 16437: Removes context items when projects are not editable by user
 
             // when
             const result = getResourceWithEditableStatus(id, userUuid)(resourcesState);
@@ -106,8 +160,13 @@ describe('resources', () => {
 
         it('should return not editable project resource', () => {
             // given
+<<<<<<< HEAD
             const id = groupFixtures.not_editable_project_resource_uuid;
             const userUuid = groupFixtures.user_uuid;
+=======
+            const id = 'zzzzz-j7d0g-0123456789ab321';
+            const userUuid = 'zzzzz-tpzed-0123456789ab789';
+>>>>>>> ea0b48ab... 16437: Removes context items when projects are not editable by user
 
             // when
             const result = getResourceWithEditableStatus(id, userUuid)(resourcesState);
@@ -118,8 +177,13 @@ describe('resources', () => {
 
         it('should return not editable collection resource', () => {
             // given
+<<<<<<< HEAD
             const id = groupFixtures.not_editable_collection_resource_uuid;
             const userUuid = groupFixtures.user_uuid;
+=======
+            const id = 'zzzzz-4zz18-0123456789ab654';
+            const userUuid = 'zzzzz-tpzed-0123456789ab789';
+>>>>>>> ea0b48ab... 16437: Removes context items when projects are not editable by user
 
             // when
             const result = getResourceWithEditableStatus(id, userUuid)(resourcesState);

commit 0571951f6c77ec7b73eba240c117a05a50fe6ca0
Author: Daniel Kutyła <daniel.kutyla at contractors.roche.com>
Date:   Mon Jun 15 22:06:51 2020 +0200

    16437: Added test fixtures, fixed context menu for favorites panel
    
    Arvados-DCO-1.1-Signed-off-by: Daniel Kutyła <daniel.kutyla at contractors.roche.com>

diff --git a/src/common/test/group-fixtures.ts b/src/common/test/group-fixtures.ts
new file mode 100644
index 00000000..d68a49b3
--- /dev/null
+++ b/src/common/test/group-fixtures.ts
@@ -0,0 +1,13 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+export default {
+  user_uuid: 'zzzzz-tpzed-0123456789ab789',
+  user_resource_uuid: 'zzzzz-tpzed-0123456789abcde',
+  unknown_user_resource_uuid: 'zzzzz-tpzed-0123456789ab987',
+  editable_collection_resource_uuid: 'zzzzz-4zz18-0123456789ab456',
+  not_editable_collection_resource_uuid: 'zzzzz-4zz18-0123456789ab654',
+  editable_project_resource_uuid: 'zzzzz-j7d0g-0123456789ab123',
+  not_editable_project_resource_uuid: 'zzzzz-j7d0g-0123456789ab321',
+};
diff --git a/src/store/resources/resources.test.ts b/src/store/resources/resources.test.ts
index a4c12039..1dfeaf96 100644
--- a/src/store/resources/resources.test.ts
+++ b/src/store/resources/resources.test.ts
@@ -4,25 +4,26 @@
 
 import { getResourceWithEditableStatus } from "./resources";
 import { ResourceKind } from "~/models/resource";
+import groupFixtures from "../../common/test/group-fixtures";
 
 describe('resources', () => {
     describe('getResourceWithEditableStatus', () => {
         const resourcesState = {
-            'zzzzz-j7d0g-0123456789ab123': {
-                uuid: 'zzzzz-j7d0g-0123456789ab123',
-                ownerUuid: 'zzzzz-tpzed-0123456789ab789',
+            [groupFixtures.editable_project_resource_uuid]: {
+                uuid: groupFixtures.editable_project_resource_uuid,
+                ownerUuid: groupFixtures.user_uuid,
                 createdAt: 'string',
                 modifiedByClientUuid: 'string',
                 modifiedByUserUuid: 'string',
                 modifiedAt: 'string',
                 href: 'string',
                 kind: ResourceKind.PROJECT,
-                writableBy: ['zzzzz-tpzed-0123456789ab789'],
+                writableBy: [groupFixtures.user_uuid],
                 etag: 'string',
             },
-            'zzzzz-4zz18-0123456789ab456': {
-                uuid: 'zzzzz-4zz18-0123456789ab456',
-                ownerUuid: 'zzzzz-j7d0g-0123456789ab123',
+            [groupFixtures.editable_collection_resource_uuid]: {
+                uuid: groupFixtures.editable_collection_resource_uuid,
+                ownerUuid: groupFixtures.editable_project_resource_uuid,
                 createdAt: 'string',
                 modifiedByClientUuid: 'string',
                 modifiedByUserUuid: 'string',
@@ -31,21 +32,21 @@ describe('resources', () => {
                 kind: ResourceKind.COLLECTION,
                 etag: 'string',
             },
-            'zzzzz-j7d0g-0123456789ab321': {
-                uuid: 'zzzzz-j7d0g-0123456789ab321',
-                ownerUuid: 'zzzzz-tpzed-0123456789ab987',
+            [groupFixtures.not_editable_project_resource_uuid]: {
+                uuid: groupFixtures.not_editable_project_resource_uuid,
+                ownerUuid: groupFixtures.unknown_user_resource_uuid,
                 createdAt: 'string',
                 modifiedByClientUuid: 'string',
                 modifiedByUserUuid: 'string',
                 modifiedAt: 'string',
                 href: 'string',
                 kind: ResourceKind.PROJECT,
-                writableBy: ['zzzzz-tpzed-0123456789ab987'],
+                writableBy: [groupFixtures.unknown_user_resource_uuid],
                 etag: 'string',
             },
-            'zzzzz-4zz18-0123456789ab654': {
-                uuid: 'zzzzz-4zz18-0123456789ab654',
-                ownerUuid: 'zzzzz-j7d0g-0123456789ab321',
+            [groupFixtures.not_editable_collection_resource_uuid]: {
+                uuid: groupFixtures.not_editable_collection_resource_uuid,
+                ownerUuid: groupFixtures.not_editable_project_resource_uuid,
                 createdAt: 'string',
                 modifiedByClientUuid: 'string',
                 modifiedByUserUuid: 'string',
@@ -54,9 +55,9 @@ describe('resources', () => {
                 kind: ResourceKind.COLLECTION,
                 etag: 'string',
             },
-            'zzzzz-tpzed-0123456789abcde': {
-                uuid: 'zzzzz-tpzed-0123456789abcde',
-                ownerUuid: 'zzzzz-tpzed-0123456789abcde',
+            [groupFixtures.user_resource_uuid]: {
+                uuid: groupFixtures.user_resource_uuid,
+                ownerUuid: groupFixtures.user_resource_uuid,
                 createdAt: 'string',
                 modifiedByClientUuid: 'string',
                 modifiedByUserUuid: 'string',
@@ -69,8 +70,8 @@ describe('resources', () => {
 
         it('should return editable user resource (resource UUID is equal to user UUID)', () => {
             // given
-            const id = 'zzzzz-tpzed-0123456789abcde';
-            const userUuid = 'zzzzz-tpzed-0123456789abcde';
+            const id = groupFixtures.user_resource_uuid;
+            const userUuid = groupFixtures.user_resource_uuid;
 
             // when
             const result = getResourceWithEditableStatus(id, userUuid)(resourcesState);
@@ -81,8 +82,8 @@ describe('resources', () => {
 
         it('should return editable project resource', () => {
             // given
-            const id = 'zzzzz-j7d0g-0123456789ab123';
-            const userUuid = 'zzzzz-tpzed-0123456789ab789';
+            const id = groupFixtures.editable_project_resource_uuid;
+            const userUuid = groupFixtures.user_uuid;
 
             // when
             const result = getResourceWithEditableStatus(id, userUuid)(resourcesState);
@@ -93,8 +94,8 @@ describe('resources', () => {
 
         it('should return editable collection resource', () => {
             // given
-            const id = 'zzzzz-4zz18-0123456789ab456';
-            const userUuid = 'zzzzz-tpzed-0123456789ab789';
+            const id = groupFixtures.editable_collection_resource_uuid;
+            const userUuid = groupFixtures.user_uuid;
 
             // when
             const result = getResourceWithEditableStatus(id, userUuid)(resourcesState);
@@ -105,8 +106,8 @@ describe('resources', () => {
 
         it('should return not editable project resource', () => {
             // given
-            const id = 'zzzzz-j7d0g-0123456789ab321';
-            const userUuid = 'zzzzz-tpzed-0123456789ab789';
+            const id = groupFixtures.not_editable_project_resource_uuid;
+            const userUuid = groupFixtures.user_uuid;
 
             // when
             const result = getResourceWithEditableStatus(id, userUuid)(resourcesState);
@@ -117,8 +118,8 @@ describe('resources', () => {
 
         it('should return not editable collection resource', () => {
             // given
-            const id = 'zzzzz-4zz18-0123456789ab654';
-            const userUuid = 'zzzzz-tpzed-0123456789ab789';
+            const id = groupFixtures.not_editable_collection_resource_uuid;
+            const userUuid = groupFixtures.user_uuid;
 
             // when
             const result = getResourceWithEditableStatus(id, userUuid)(resourcesState);
diff --git a/src/store/resources/resources.ts b/src/store/resources/resources.ts
index 1012209c..8fb6b5ca 100644
--- a/src/store/resources/resources.ts
+++ b/src/store/resources/resources.ts
@@ -26,7 +26,7 @@ const getResourceWritableBy = (state: ResourcesState, id: string, userUuid: stri
 
     const { writableBy } = resource;
 
-    return writableBy ? writableBy : getResourceWritableBy(state, resource.ownerUuid, userUuid);
+    return writableBy || getResourceWritableBy(state, resource.ownerUuid, userUuid);
 };
 
 export const getResourceWithEditableStatus = <T extends EditableResource & GroupResource>(id: string, userUuid?: string) => 
diff --git a/src/views/favorite-panel/favorite-panel.tsx b/src/views/favorite-panel/favorite-panel.tsx
index e7234009..9c906eef 100644
--- a/src/views/favorite-panel/favorite-panel.tsx
+++ b/src/views/favorite-panel/favorite-panel.tsx
@@ -10,7 +10,7 @@ import { DataColumns } from '~/components/data-table/data-table';
 import { RouteComponentProps } from 'react-router';
 import { DataTableFilterItem } from '~/components/data-table-filters/data-table-filters';
 import { SortDirection } from '~/components/data-table/data-column';
-import { ResourceKind } from '~/models/resource';
+import { ResourceKind, EditableResource } from '~/models/resource';
 import { ArvadosTheme } from '~/common/custom-theme';
 import { FAVORITE_PANEL_ID } from "~/store/favorite-panel/favorite-panel-action";
 import {
@@ -31,6 +31,8 @@ import { RootState } from '~/store/store';
 import { DataTableDefaultView } from '~/components/data-table-default-view/data-table-default-view';
 import { createTree } from '~/models/tree';
 import { getSimpleObjectTypeFilters } from '~/store/resource-type-filters/resource-type-filters';
+import { getResourceWithEditableStatus, ResourcesState } from '~/store/resources/resources';
+import { ProjectResource } from '~/models/project';
 
 type CssRules = "toolbar" | "button";
 
@@ -106,7 +108,9 @@ export const favoritePanelColumns: DataColumns<string> = [
 
 interface FavoritePanelDataProps {
     favorites: FavoritesState;
+    resources: ResourcesState;
     isAdmin: boolean;
+    userUuid: string;
 }
 
 interface FavoritePanelActionProps {
@@ -116,7 +120,9 @@ interface FavoritePanelActionProps {
 }
 const mapStateToProps = (state : RootState): FavoritePanelDataProps => ({
     favorites: state.favorites,
-    isAdmin: state.auth.user!.isAdmin
+    resources: state.resources,
+    isAdmin: state.auth.user!.isAdmin,
+    userUuid: state.auth.user!.uuid,
 });
 
 type FavoritePanelProps = FavoritePanelDataProps & FavoritePanelActionProps & DispatchProp
@@ -127,7 +133,9 @@ export const FavoritePanel = withStyles(styles)(
         class extends React.Component<FavoritePanelProps> {
 
             handleContextMenu = (event: React.MouseEvent<HTMLElement>, resourceUuid: string) => {
-                const menuKind = resourceKindToContextMenuKind(resourceUuid, this.props.isAdmin);
+                const { isAdmin, userUuid, resources } = this.props;
+                const resource = getResourceWithEditableStatus<ProjectResource & EditableResource>(resourceUuid, userUuid)(resources);
+                const menuKind = resourceKindToContextMenuKind(resourceUuid, isAdmin, (resource || {} as EditableResource).isEditable);
                 if (menuKind) {
                     this.props.dispatch<any>(openContextMenu(event, {
                         name: '',

commit 3f4b4e61c725b05f6b4ec1ddd2ef1826274fc0e0
Author: Daniel Kutyła <daniel.kutyla at contractors.roche.com>
Date:   Fri Jun 5 22:35:40 2020 +0200

    16437: Removes context items when projects are not editable by user
    
    Arvados-DCO-1.1-Signed-off-by: Daniel Kutyła <daniel.kutyla at contractors.roche.com>

diff --git a/src/index.tsx b/src/index.tsx
index a12dabfa..2cee0540 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -21,7 +21,7 @@ import { CustomTheme } from '~/common/custom-theme';
 import { fetchConfig } from '~/common/config';
 import { addMenuActionSet, ContextMenuKind } from '~/views-components/context-menu/context-menu';
 import { rootProjectActionSet } from "~/views-components/context-menu/action-sets/root-project-action-set";
-import { projectActionSet } from "~/views-components/context-menu/action-sets/project-action-set";
+import { 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';
@@ -67,6 +67,7 @@ console.log(`Starting arvados [${getBuildInfo()}]`);
 
 addMenuActionSet(ContextMenuKind.ROOT_PROJECT, rootProjectActionSet);
 addMenuActionSet(ContextMenuKind.PROJECT, projectActionSet);
+addMenuActionSet(ContextMenuKind.READONLY_PROJECT, readOnlyProjectActionSet);
 addMenuActionSet(ContextMenuKind.RESOURCE, resourceActionSet);
 addMenuActionSet(ContextMenuKind.FAVORITE, favoriteActionSet);
 addMenuActionSet(ContextMenuKind.COLLECTION_FILES, collectionFilesActionSet);
diff --git a/src/models/resource.ts b/src/models/resource.ts
index 4708a9da..d8cdd4a0 100644
--- a/src/models/resource.ts
+++ b/src/models/resource.ts
@@ -14,6 +14,10 @@ export interface Resource {
     etag: string;
 }
 
+export interface EditableResource extends Resource {
+    isEditable: boolean;
+}
+
 export interface TrashableResource extends Resource {
     trashAt: string;
     deleteAt: string;
diff --git a/src/store/context-menu/context-menu-actions.test.ts b/src/store/context-menu/context-menu-actions.test.ts
new file mode 100644
index 00000000..04657e90
--- /dev/null
+++ b/src/store/context-menu/context-menu-actions.test.ts
@@ -0,0 +1,161 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as resource from '~/models/resource';
+import { ContextMenuKind } from '~/views-components/context-menu/context-menu';
+import { resourceKindToContextMenuKind } from './context-menu-actions';
+
+describe('context-menu-actions', () => {
+    describe('resourceKindToContextMenuKind', () => {
+        const uuid = '123';
+
+        describe('ResourceKind.PROJECT', () => {
+            beforeEach(() => {
+                // setup
+                jest.spyOn(resource, 'extractUuidKind')
+                    .mockImplementation(() => resource.ResourceKind.PROJECT);
+            });
+
+            it('should return ContextMenuKind.PROJECT_ADMIN', () => {
+                // given
+                const isAdmin = true;
+    
+                // when
+                const result = resourceKindToContextMenuKind(uuid, isAdmin);
+    
+                // then
+                expect(result).toEqual(ContextMenuKind.PROJECT_ADMIN);
+            });
+    
+            it('should return ContextMenuKind.PROJECT', () => {
+                // given
+                const isAdmin = false;
+                const isEditable = true;
+    
+                // when
+                const result = resourceKindToContextMenuKind(uuid, isAdmin, isEditable);
+    
+                // then
+                expect(result).toEqual(ContextMenuKind.PROJECT);
+            });
+    
+            it('should return ContextMenuKind.READONLY_PROJECT', () => {
+                // given
+                const isAdmin = false;
+                const isEditable = false;
+    
+                // when
+                const result = resourceKindToContextMenuKind(uuid, isAdmin, isEditable);
+    
+                // then
+                expect(result).toEqual(ContextMenuKind.READONLY_PROJECT);
+            });
+        });
+
+        describe('ResourceKind.COLLECTION', () => {
+            beforeEach(() => {
+                // setup
+                jest.spyOn(resource, 'extractUuidKind')
+                    .mockImplementation(() => resource.ResourceKind.COLLECTION);
+            });
+
+            it('should return ContextMenuKind.COLLECTION_ADMIN', () => {
+                // given
+                const isAdmin = true;
+    
+                // when
+                const result = resourceKindToContextMenuKind(uuid, isAdmin);
+    
+                // then
+                expect(result).toEqual(ContextMenuKind.COLLECTION_ADMIN);
+            });
+    
+            it('should return ContextMenuKind.COLLECTION_RESOURCE', () => {
+                // given
+                const isAdmin = false;
+                const isEditable = true;
+    
+                // when
+                const result = resourceKindToContextMenuKind(uuid, isAdmin, isEditable);
+    
+                // then
+                expect(result).toEqual(ContextMenuKind.COLLECTION_RESOURCE);
+            });
+    
+            it('should return ContextMenuKind.READONLY_COLLECTION', () => {
+                // given
+                const isAdmin = false;
+                const isEditable = false;
+    
+                // when
+                const result = resourceKindToContextMenuKind(uuid, isAdmin, isEditable);
+    
+                // then
+                expect(result).toEqual(ContextMenuKind.READONLY_COLLECTION);
+            });
+        });
+
+        describe('ResourceKind.PROCESS', () => {
+            beforeEach(() => {
+                // setup
+                jest.spyOn(resource, 'extractUuidKind')
+                    .mockImplementation(() => resource.ResourceKind.PROCESS);
+            });
+
+            it('should return ContextMenuKind.PROCESS_ADMIN', () => {
+                // given
+                const isAdmin = true;
+    
+                // when
+                const result = resourceKindToContextMenuKind(uuid, isAdmin);
+    
+                // then
+                expect(result).toEqual(ContextMenuKind.PROCESS_ADMIN);
+            });
+
+            it('should return ContextMenuKind.PROCESS_RESOURCE', () => {
+                // given
+                const isAdmin = false;
+    
+                // when
+                const result = resourceKindToContextMenuKind(uuid, isAdmin);
+    
+                // then
+                expect(result).toEqual(ContextMenuKind.PROCESS_RESOURCE);
+            });
+        });
+
+        describe('ResourceKind.USER', () => {
+            beforeEach(() => {
+                // setup
+                jest.spyOn(resource, 'extractUuidKind')
+                    .mockImplementation(() => resource.ResourceKind.USER);
+            });
+
+            it('should return ContextMenuKind.ROOT_PROJECT', () => {    
+                // when
+                const result = resourceKindToContextMenuKind(uuid);
+    
+                // then
+                expect(result).toEqual(ContextMenuKind.ROOT_PROJECT);
+            });
+        });
+
+        describe('ResourceKind.LINK', () => {
+            beforeEach(() => {
+                // setup
+                jest.spyOn(resource, 'extractUuidKind')
+                    .mockImplementation(() => resource.ResourceKind.LINK);
+            });
+
+            it('should return ContextMenuKind.LINK', () => {    
+                // when
+                const result = resourceKindToContextMenuKind(uuid);
+    
+                // then
+                expect(result).toEqual(ContextMenuKind.LINK);
+            });
+        });
+    });
+});
\ No newline at end of file
diff --git a/src/store/context-menu/context-menu-actions.ts b/src/store/context-menu/context-menu-actions.ts
index 2ba6bc2c..1f766bd3 100644
--- a/src/store/context-menu/context-menu-actions.ts
+++ b/src/store/context-menu/context-menu-actions.ts
@@ -7,11 +7,11 @@ 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 } from '../resources/resources';
+import { getResource, getResourceWithEditableStatus } from '../resources/resources';
 import { ProjectResource } from '~/models/project';
 import { UserResource } from '~/models/user';
 import { isSidePanelTreeCategory } from '~/store/side-panel-tree/side-panel-tree-actions';
-import { extractUuidKind, ResourceKind } from '~/models/resource';
+import { extractUuidKind, ResourceKind, EditableResource } from '~/models/resource';
 import { Process } from '~/store/processes/process';
 import { RepositoryResource } from '~/models/repositories';
 import { SshKeyResource } from '~/models/ssh-key';
@@ -34,6 +34,7 @@ export type ContextMenuResource = {
     kind: ResourceKind,
     menuKind: ContextMenuKind;
     isTrashed?: boolean;
+    isEditable?: boolean;
     outputUuid?: string;
     workflowUuid?: string;
 };
@@ -153,16 +154,17 @@ export const openRootProjectContextMenu = (event: React.MouseEvent<HTMLElement>,
         }
     };
 
-export const openProjectContextMenu = (event: React.MouseEvent<HTMLElement>, projectUuid: string) =>
+export const openProjectContextMenu = (event: React.MouseEvent<HTMLElement>, resourceUuid: string) =>
     (dispatch: Dispatch, getState: () => RootState) => {
-        const res = getResource<ProjectResource>(projectUuid)(getState().resources);
-        const isAdmin = getState().auth.user!.isAdmin;
-        if (res) {
+        const { isAdmin, uuid: userUuid } = getState().auth.user!;
+        const res = getResourceWithEditableStatus<ProjectResource & EditableResource>(resourceUuid, userUuid)(getState().resources);
+        const menuKind = resourceKindToContextMenuKind(resourceUuid, isAdmin, (res || {} as EditableResource).isEditable);
+        if (res && menuKind) {
             dispatch<any>(openContextMenu(event, {
                 name: res.name,
                 uuid: res.uuid,
                 kind: res.kind,
-                menuKind: !isAdmin ? ContextMenuKind.PROJECT : ContextMenuKind.PROJECT_ADMIN,
+                menuKind,
                 ownerUuid: res.ownerUuid,
                 isTrashed: res.isTrashed
             }));
@@ -198,13 +200,17 @@ export const openProcessContextMenu = (event: React.MouseEvent<HTMLElement>, pro
         }
     };
 
-export const resourceKindToContextMenuKind = (uuid: string, isAdmin?: boolean) => {
+export const resourceKindToContextMenuKind = (uuid: string, isAdmin?: boolean, isEditable?: boolean) => {
     const kind = extractUuidKind(uuid);
     switch (kind) {
         case ResourceKind.PROJECT:
-            return !isAdmin ? ContextMenuKind.PROJECT : ContextMenuKind.PROJECT_ADMIN;
+            return !isAdmin ?
+                isEditable ? ContextMenuKind.PROJECT : ContextMenuKind.READONLY_PROJECT :
+                ContextMenuKind.PROJECT_ADMIN;
         case ResourceKind.COLLECTION:
-            return !isAdmin ? ContextMenuKind.COLLECTION_RESOURCE : ContextMenuKind.COLLECTION_ADMIN;
+            return !isAdmin ?
+                isEditable ? ContextMenuKind.COLLECTION_RESOURCE : ContextMenuKind.READONLY_COLLECTION :
+                ContextMenuKind.COLLECTION_ADMIN;
         case ResourceKind.PROCESS:
             return !isAdmin ? ContextMenuKind.PROCESS_RESOURCE : ContextMenuKind.PROCESS_ADMIN;
         case ResourceKind.USER:
diff --git a/src/store/resources/resources.test.ts b/src/store/resources/resources.test.ts
new file mode 100644
index 00000000..a4c12039
--- /dev/null
+++ b/src/store/resources/resources.test.ts
@@ -0,0 +1,130 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { getResourceWithEditableStatus } from "./resources";
+import { ResourceKind } from "~/models/resource";
+
+describe('resources', () => {
+    describe('getResourceWithEditableStatus', () => {
+        const resourcesState = {
+            'zzzzz-j7d0g-0123456789ab123': {
+                uuid: 'zzzzz-j7d0g-0123456789ab123',
+                ownerUuid: 'zzzzz-tpzed-0123456789ab789',
+                createdAt: 'string',
+                modifiedByClientUuid: 'string',
+                modifiedByUserUuid: 'string',
+                modifiedAt: 'string',
+                href: 'string',
+                kind: ResourceKind.PROJECT,
+                writableBy: ['zzzzz-tpzed-0123456789ab789'],
+                etag: 'string',
+            },
+            'zzzzz-4zz18-0123456789ab456': {
+                uuid: 'zzzzz-4zz18-0123456789ab456',
+                ownerUuid: 'zzzzz-j7d0g-0123456789ab123',
+                createdAt: 'string',
+                modifiedByClientUuid: 'string',
+                modifiedByUserUuid: 'string',
+                modifiedAt: 'string',
+                href: 'string',
+                kind: ResourceKind.COLLECTION,
+                etag: 'string',
+            },
+            'zzzzz-j7d0g-0123456789ab321': {
+                uuid: 'zzzzz-j7d0g-0123456789ab321',
+                ownerUuid: 'zzzzz-tpzed-0123456789ab987',
+                createdAt: 'string',
+                modifiedByClientUuid: 'string',
+                modifiedByUserUuid: 'string',
+                modifiedAt: 'string',
+                href: 'string',
+                kind: ResourceKind.PROJECT,
+                writableBy: ['zzzzz-tpzed-0123456789ab987'],
+                etag: 'string',
+            },
+            'zzzzz-4zz18-0123456789ab654': {
+                uuid: 'zzzzz-4zz18-0123456789ab654',
+                ownerUuid: 'zzzzz-j7d0g-0123456789ab321',
+                createdAt: 'string',
+                modifiedByClientUuid: 'string',
+                modifiedByUserUuid: 'string',
+                modifiedAt: 'string',
+                href: 'string',
+                kind: ResourceKind.COLLECTION,
+                etag: 'string',
+            },
+            'zzzzz-tpzed-0123456789abcde': {
+                uuid: 'zzzzz-tpzed-0123456789abcde',
+                ownerUuid: 'zzzzz-tpzed-0123456789abcde',
+                createdAt: 'string',
+                modifiedByClientUuid: 'string',
+                modifiedByUserUuid: 'string',
+                modifiedAt: 'string',
+                href: 'string',
+                kind: ResourceKind.USER,
+                etag: 'string', 
+            }
+        };
+
+        it('should return editable user resource (resource UUID is equal to user UUID)', () => {
+            // given
+            const id = 'zzzzz-tpzed-0123456789abcde';
+            const userUuid = 'zzzzz-tpzed-0123456789abcde';
+
+            // when
+            const result = getResourceWithEditableStatus(id, userUuid)(resourcesState);
+
+            // then
+            expect(result!.isEditable).toBeTruthy();
+        });
+
+        it('should return editable project resource', () => {
+            // given
+            const id = 'zzzzz-j7d0g-0123456789ab123';
+            const userUuid = 'zzzzz-tpzed-0123456789ab789';
+
+            // when
+            const result = getResourceWithEditableStatus(id, userUuid)(resourcesState);
+
+            // then
+            expect(result!.isEditable).toBeTruthy();
+        });
+
+        it('should return editable collection resource', () => {
+            // given
+            const id = 'zzzzz-4zz18-0123456789ab456';
+            const userUuid = 'zzzzz-tpzed-0123456789ab789';
+
+            // when
+            const result = getResourceWithEditableStatus(id, userUuid)(resourcesState);
+
+            // then
+            expect(result!.isEditable).toBeTruthy();
+        });
+
+        it('should return not editable project resource', () => {
+            // given
+            const id = 'zzzzz-j7d0g-0123456789ab321';
+            const userUuid = 'zzzzz-tpzed-0123456789ab789';
+
+            // when
+            const result = getResourceWithEditableStatus(id, userUuid)(resourcesState);
+
+            // then
+            expect(result!.isEditable).toBeFalsy();
+        });
+
+        it('should return not editable collection resource', () => {
+            // given
+            const id = 'zzzzz-4zz18-0123456789ab654';
+            const userUuid = 'zzzzz-tpzed-0123456789ab789';
+
+            // when
+            const result = getResourceWithEditableStatus(id, userUuid)(resourcesState);
+
+            // then
+            expect(result!.isEditable).toBeFalsy();
+        });
+    });
+});
\ No newline at end of file
diff --git a/src/store/resources/resources.ts b/src/store/resources/resources.ts
index e7153dec..1012209c 100644
--- a/src/store/resources/resources.ts
+++ b/src/store/resources/resources.ts
@@ -2,11 +2,44 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { Resource } from "~/models/resource";
+import { Resource, EditableResource } from "~/models/resource";
 import { ResourceKind } from '~/models/resource';
+import { ProjectResource } from "~/models/project";
+import { GroupResource } from "~/models/group";
 
 export type ResourcesState = { [key: string]: Resource };
 
+const getResourceWritableBy = (state: ResourcesState, id: string, userUuid: string): string[] => {
+    if (!id) {
+        return [];
+    }
+
+    if (id === userUuid) {
+        return [userUuid];
+    }
+
+    const resource = (state[id] as ProjectResource);
+
+    if (!resource) {
+        return [];
+    }
+
+    const { writableBy } = resource;
+
+    return writableBy ? writableBy : getResourceWritableBy(state, resource.ownerUuid, userUuid);
+};
+
+export const getResourceWithEditableStatus = <T extends EditableResource & GroupResource>(id: string, userUuid?: string) => 
+    (state: ResourcesState): T | undefined => {
+        const resource = JSON.parse(JSON.stringify(state[id] as T));
+
+        if (resource) {
+            resource.isEditable = userUuid ? getResourceWritableBy(state, id, userUuid).indexOf(userUuid) > -1 : false;
+        }
+
+        return resource;
+    };
+
 export const getResource = <T extends Resource = Resource>(id: string) =>
     (state: ResourcesState): T | undefined =>
         state[id] as T;
diff --git a/src/views-components/context-menu/action-sets/collection-action-set.test.ts b/src/views-components/context-menu/action-sets/collection-action-set.test.ts
new file mode 100644
index 00000000..9182f3f2
--- /dev/null
+++ b/src/views-components/context-menu/action-sets/collection-action-set.test.ts
@@ -0,0 +1,35 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { collectionActionSet, readOnlyCollectionActionSet } from "./collection-action-set";
+
+describe('collection-action-set', () => {
+    const flattCollectionActionSet = collectionActionSet.reduce((prev, next) => prev.concat(next), []);
+    const flattReadOnlyCollectionActionSet = readOnlyCollectionActionSet.reduce((prev, next) => prev.concat(next), []);
+    describe('collectionActionSet', () => {
+        it('should not be empty', () => {
+            // then
+            expect(flattCollectionActionSet.length).toBeGreaterThan(0);
+        });
+
+        it('should contain readOnlyCollectionActionSet items', () => {
+            // then
+            expect(flattCollectionActionSet)
+                .toEqual(expect.arrayContaining(flattReadOnlyCollectionActionSet));
+        })
+    });
+
+    describe('readOnlyCollectionActionSet', () => {
+        it('should not be empty', () => {
+            // then
+            expect(flattReadOnlyCollectionActionSet.length).toBeGreaterThan(0);
+        });
+
+        it('should not contain collectionActionSet items', () => {
+            // then
+            expect(flattReadOnlyCollectionActionSet)
+                .not.toEqual(expect.arrayContaining(flattCollectionActionSet));
+        })
+    });
+});
\ No newline at end of file
diff --git a/src/views-components/context-menu/action-sets/collection-action-set.ts b/src/views-components/context-menu/action-sets/collection-action-set.ts
index ea97a9b1..fba2a53a 100644
--- a/src/views-components/context-menu/action-sets/collection-action-set.ts
+++ b/src/views-components/context-menu/action-sets/collection-action-set.ts
@@ -49,30 +49,33 @@ export const readOnlyCollectionActionSet: ContextMenuActionSet = [[
     },
 ]];
 
-export const collectionActionSet: ContextMenuActionSet = readOnlyCollectionActionSet.concat([[
-    {
-        icon: RenameIcon,
-        name: "Edit collection",
-        execute: (dispatch, resource) => {
-            dispatch<any>(openCollectionUpdateDialog(resource));
-        }
-    },
-    {
-        icon: ShareIcon,
-        name: "Share",
-        execute: (dispatch, { uuid }) => {
-            dispatch<any>(openSharingDialog(uuid));
-        }
-    },
-    {
-        icon: MoveToIcon,
-        name: "Move to",
-        execute: (dispatch, resource) => dispatch<any>(openMoveCollectionDialog(resource))
-    },
-    {
-        component: ToggleTrashAction,
-        execute: (dispatch, resource) => {
-            dispatch<any>(toggleCollectionTrashed(resource.uuid, resource.isTrashed!!));
-        }
-    },
-]]);
+export const collectionActionSet: ContextMenuActionSet = [
+    [
+        ...readOnlyCollectionActionSet.reduce((prev, next) => prev.concat(next), []),
+        {
+            icon: RenameIcon,
+            name: "Edit collection",
+            execute: (dispatch, resource) => {
+                dispatch<any>(openCollectionUpdateDialog(resource));
+            }
+        },
+        {
+            icon: ShareIcon,
+            name: "Share",
+            execute: (dispatch, { uuid }) => {
+                dispatch<any>(openSharingDialog(uuid));
+            }
+        },
+        {
+            icon: MoveToIcon,
+            name: "Move to",
+            execute: (dispatch, resource) => dispatch<any>(openMoveCollectionDialog(resource))
+        },
+        {
+            component: ToggleTrashAction,
+            execute: (dispatch, resource) => {
+                dispatch<any>(toggleCollectionTrashed(resource.uuid, resource.isTrashed!!));
+            }
+        },
+    ]
+];
diff --git a/src/views-components/context-menu/action-sets/project-action-set.test.ts b/src/views-components/context-menu/action-sets/project-action-set.test.ts
new file mode 100644
index 00000000..fd328221
--- /dev/null
+++ b/src/views-components/context-menu/action-sets/project-action-set.test.ts
@@ -0,0 +1,36 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { projectActionSet, readOnlyProjectActionSet } from "./project-action-set";
+
+describe('project-action-set', () => {
+    const flattProjectActionSet = projectActionSet.reduce((prev, next) => prev.concat(next), []);
+    const flattReadOnlyProjectActionSet = readOnlyProjectActionSet.reduce((prev, next) => prev.concat(next), []);
+
+    describe('projectActionSet', () => {
+        it('should not be empty', () => {
+            // then
+            expect(flattProjectActionSet.length).toBeGreaterThan(0);
+        });
+
+        it('should contain readOnlyProjectActionSet items', () => {
+            // then
+            expect(flattProjectActionSet)
+                .toEqual(expect.arrayContaining(flattReadOnlyProjectActionSet));
+        })
+    });
+
+    describe('readOnlyProjectActionSet', () => {
+        it('should not be empty', () => {
+            // then
+            expect(flattReadOnlyProjectActionSet.length).toBeGreaterThan(0);
+        });
+
+        it('should not contain projectActionSet items', () => {
+            // then
+            expect(flattReadOnlyProjectActionSet)
+                .not.toEqual(expect.arrayContaining(flattProjectActionSet));
+        })
+    });
+});
\ No newline at end of file
diff --git a/src/views-components/context-menu/action-sets/project-action-set.ts b/src/views-components/context-menu/action-sets/project-action-set.ts
index 32616fce..4f92aeb8 100644
--- a/src/views-components/context-menu/action-sets/project-action-set.ts
+++ b/src/views-components/context-menu/action-sets/project-action-set.ts
@@ -17,28 +17,7 @@ import { openSharingDialog } from "~/store/sharing-dialog/sharing-dialog-actions
 import { openAdvancedTabDialog } from "~/store/advanced-tab/advanced-tab";
 import { toggleDetailsPanel } from '~/store/details-panel/details-panel-action';
 
-export const projectActionSet: ContextMenuActionSet = [[
-    {
-        icon: NewProjectIcon,
-        name: "New project",
-        execute: (dispatch, resource) => {
-            dispatch<any>(openProjectCreateDialog(resource.uuid));
-        }
-    },
-    {
-        icon: RenameIcon,
-        name: "Edit project",
-        execute: (dispatch, resource) => {
-            dispatch<any>(openProjectUpdateDialog(resource));
-        }
-    },
-    {
-        icon: ShareIcon,
-        name: "Share",
-        execute: (dispatch, { uuid }) => {
-            dispatch<any>(openSharingDialog(uuid));
-        }
-    },
+export const readOnlyProjectActionSet: ContextMenuActionSet = [[
     {
         component: ToggleFavoriteAction,
         execute: (dispatch, resource) => {
@@ -47,20 +26,6 @@ export const projectActionSet: ContextMenuActionSet = [[
             });
         }
     },
-    {
-        icon: MoveToIcon,
-        name: "Move to",
-        execute: (dispatch, resource) => {
-            dispatch<any>(openMoveProjectDialog(resource));
-        }
-    },
-    // {
-    //     icon: CopyIcon,
-    //     name: "Copy to project",
-    //     execute: (dispatch, resource) => {
-    //         // add code
-    //     }
-    // },
     {
         icon: DetailsIcon,
         name: "View details",
@@ -75,10 +40,44 @@ export const projectActionSet: ContextMenuActionSet = [[
             dispatch<any>(openAdvancedTabDialog(resource.uuid));
         }
     },
-    {
-        component: ToggleTrashAction,
-        execute: (dispatch, resource) => {
-            dispatch<any>(toggleProjectTrashed(resource.uuid, resource.ownerUuid, resource.isTrashed!!));
-        }
-    },
 ]];
+
+export const projectActionSet: ContextMenuActionSet = [
+    [
+        ...readOnlyProjectActionSet.reduce((prev, next) => prev.concat(next), []),
+        {
+            icon: NewProjectIcon,
+            name: "New project",
+            execute: (dispatch, resource) => {
+                dispatch<any>(openProjectCreateDialog(resource.uuid));
+            }
+        },
+        {
+            icon: RenameIcon,
+            name: "Edit project",
+            execute: (dispatch, resource) => {
+                dispatch<any>(openProjectUpdateDialog(resource));
+            }
+        },
+        {
+            icon: ShareIcon,
+            name: "Share",
+            execute: (dispatch, { uuid }) => {
+                dispatch<any>(openSharingDialog(uuid));
+            }
+        },
+        {
+            icon: MoveToIcon,
+            name: "Move to",
+            execute: (dispatch, resource) => {
+                dispatch<any>(openMoveProjectDialog(resource));
+            }
+        },
+        {
+            component: ToggleTrashAction,
+            execute: (dispatch, resource) => {
+                dispatch<any>(toggleProjectTrashed(resource.uuid, resource.ownerUuid, resource.isTrashed!!));
+            }
+        },
+    ]
+];
diff --git a/src/views-components/context-menu/context-menu.tsx b/src/views-components/context-menu/context-menu.tsx
index 55b0abd8..db5765ee 100644
--- a/src/views-components/context-menu/context-menu.tsx
+++ b/src/views-components/context-menu/context-menu.tsx
@@ -65,6 +65,7 @@ export enum ContextMenuKind {
     API_CLIENT_AUTHORIZATION = "ApiClientAuthorization",
     ROOT_PROJECT = "RootProject",
     PROJECT = "Project",
+    READONLY_PROJECT = 'ReadOnlyProject',
     PROJECT_ADMIN = "ProjectAdmin",
     RESOURCE = "Resource",
     FAVORITE = "Favorite",
diff --git a/src/views/project-panel/project-panel.tsx b/src/views/project-panel/project-panel.tsx
index 1e26bc0d..687e17df 100644
--- a/src/views/project-panel/project-panel.tsx
+++ b/src/views/project-panel/project-panel.tsx
@@ -14,11 +14,11 @@ import { RootState } from '~/store/store';
 import { DataTableFilterItem } from '~/components/data-table-filters/data-table-filters';
 import { ContainerRequestState } from '~/models/container-request';
 import { SortDirection } from '~/components/data-table/data-column';
-import { ResourceKind, Resource } from '~/models/resource';
+import { ResourceKind, Resource, EditableResource } from '~/models/resource';
 import { ResourceFileSize, ResourceLastModifiedDate, ProcessStatus, ResourceType, ResourceOwner } from '~/views-components/data-explorer/renderers';
 import { ProjectIcon } from '~/components/icon/icon';
 import { ResourceName } from '~/views-components/data-explorer/renderers';
-import { ResourcesState, getResource } from '~/store/resources/resources';
+import { ResourcesState, getResourceWithEditableStatus } from '~/store/resources/resources';
 import { loadDetailsPanel } from '~/store/details-panel/details-panel-action';
 import { resourceKindToContextMenuKind, openContextMenu } from '~/store/context-menu/context-menu-actions';
 import { ProjectResource } from '~/models/project';
@@ -115,6 +115,7 @@ interface ProjectPanelDataProps {
     currentItemId: string;
     resources: ResourcesState;
     isAdmin: boolean;
+    userUuid: string;
 }
 
 type ProjectPanelProps = ProjectPanelDataProps & DispatchProp
@@ -124,7 +125,8 @@ export const ProjectPanel = withStyles(styles)(
     connect((state: RootState) => ({
         currentItemId: getProperty(PROJECT_PANEL_CURRENT_UUID)(state.properties),
         resources: state.resources,
-        isAdmin: state.auth.user!.isAdmin
+        isAdmin: state.auth.user!.isAdmin,
+        userUuid: state.auth.user!.uuid,
     }))(
         class extends React.Component<ProjectPanelProps> {
             render() {
@@ -149,8 +151,9 @@ export const ProjectPanel = withStyles(styles)(
             }
 
             handleContextMenu = (event: React.MouseEvent<HTMLElement>, resourceUuid: string) => {
-                const menuKind = resourceKindToContextMenuKind(resourceUuid, this.props.isAdmin);
-                const resource = getResource<ProjectResource>(resourceUuid)(this.props.resources);
+                const { isAdmin, userUuid, resources } = this.props;
+                const resource = getResourceWithEditableStatus<ProjectResource & EditableResource>(resourceUuid, userUuid)(resources);
+                const menuKind = resourceKindToContextMenuKind(resourceUuid, isAdmin, (resource || {} as EditableResource).isEditable);
                 if (menuKind && resource) {
                     this.props.dispatch<any>(openContextMenu(event, {
                         name: resource.name,
diff --git a/src/views/shared-with-me-panel/shared-with-me-panel.tsx b/src/views/shared-with-me-panel/shared-with-me-panel.tsx
index 7fd00ba1..c9408752 100644
--- a/src/views/shared-with-me-panel/shared-with-me-panel.tsx
+++ b/src/views/shared-with-me-panel/shared-with-me-panel.tsx
@@ -9,13 +9,14 @@ import { connect, DispatchProp } from 'react-redux';
 import { RootState } from '~/store/store';
 import { ArvadosTheme } from '~/common/custom-theme';
 import { ShareMeIcon } from '~/components/icon/icon';
-import { ResourcesState, getResource } from '~/store/resources/resources';
+import { ResourcesState, getResourceWithEditableStatus } from '~/store/resources/resources';
 import { navigateTo } from "~/store/navigation/navigation-action";
 import { loadDetailsPanel } from "~/store/details-panel/details-panel-action";
 import { DataTableDefaultView } from '~/components/data-table-default-view/data-table-default-view';
 import { SHARED_WITH_ME_PANEL_ID } from '~/store/shared-with-me-panel/shared-with-me-panel-actions';
 import { openContextMenu, resourceKindToContextMenuKind } from '~/store/context-menu/context-menu-actions';
 import { GroupResource } from '~/models/group';
+import { EditableResource } from '~/models/resource';
 
 type CssRules = "toolbar" | "button";
 
@@ -32,6 +33,7 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
 interface SharedWithMePanelDataProps {
     resources: ResourcesState;
     isAdmin: boolean;
+    userUuid: string;
 }
 
 type SharedWithMePanelProps = SharedWithMePanelDataProps & DispatchProp & WithStyles<CssRules>;
@@ -39,7 +41,8 @@ type SharedWithMePanelProps = SharedWithMePanelDataProps & DispatchProp & WithSt
 export const SharedWithMePanel = withStyles(styles)(
     connect((state: RootState) => ({
         resources: state.resources,
-        isAdmin: state.auth.user!.isAdmin
+        isAdmin: state.auth.user!.isAdmin,
+        userUuid: state.auth.user!.uuid,
     }))(
         class extends React.Component<SharedWithMePanelProps> {
             render() {
@@ -53,8 +56,9 @@ export const SharedWithMePanel = withStyles(styles)(
             }
 
             handleContextMenu = (event: React.MouseEvent<HTMLElement>, resourceUuid: string) => {
-                const menuKind = resourceKindToContextMenuKind(resourceUuid, this.props.isAdmin);
-                const resource = getResource<GroupResource>(resourceUuid)(this.props.resources);
+                const { isAdmin, userUuid, resources } = this.props;
+                const resource = getResourceWithEditableStatus<GroupResource & EditableResource>(resourceUuid, userUuid)(resources);
+                const menuKind = resourceKindToContextMenuKind(resourceUuid, isAdmin, (resource || {} as EditableResource).isEditable);
                 if (menuKind && resource) {
                     this.props.dispatch<any>(openContextMenu(event, {
                         name: '',

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


hooks/post-receive
-- 




More information about the arvados-commits mailing list