16971: Fixed issue in other places
[arvados-workbench2.git] / src / store / context-menu / context-menu-actions.ts
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import { unionize, ofType, UnionOf } from '~/common/unionize';
6 import { ContextMenuPosition } from "./context-menu-reducer";
7 import { ContextMenuKind } from '~/views-components/context-menu/context-menu';
8 import { Dispatch } from 'redux';
9 import { RootState } from '~/store/store';
10 import { getResource, getResourceWithEditableStatus } from '../resources/resources';
11 import { UserResource } from '~/models/user';
12 import { isSidePanelTreeCategory } from '~/store/side-panel-tree/side-panel-tree-actions';
13 import { extractUuidKind, ResourceKind, EditableResource } from '~/models/resource';
14 import { Process } from '~/store/processes/process';
15 import { RepositoryResource } from '~/models/repositories';
16 import { SshKeyResource } from '~/models/ssh-key';
17 import { VirtualMachinesResource } from '~/models/virtual-machines';
18 import { KeepServiceResource } from '~/models/keep-services';
19 import { ProcessResource } from '~/models/process';
20 import { CollectionResource } from '~/models/collection';
21 import { GroupClass, GroupResource } from '~/models/group';
22 import { GroupContentsResource } from '~/services/groups-service/groups-service';
23
24 export const contextMenuActions = unionize({
25     OPEN_CONTEXT_MENU: ofType<{ position: ContextMenuPosition, resource: ContextMenuResource }>(),
26     CLOSE_CONTEXT_MENU: ofType<{}>()
27 });
28
29 export type ContextMenuAction = UnionOf<typeof contextMenuActions>;
30
31 export type ContextMenuResource = {
32     name: string;
33     uuid: string;
34     ownerUuid: string;
35     description?: string;
36     kind: ResourceKind,
37     menuKind: ContextMenuKind | string;
38     isTrashed?: boolean;
39     isEditable?: boolean;
40     outputUuid?: string;
41     workflowUuid?: string;
42 };
43
44 export const isKeyboardClick = (event: React.MouseEvent<HTMLElement>) => event.nativeEvent.detail === 0;
45
46 export const openContextMenu = (event: React.MouseEvent<HTMLElement>, resource: ContextMenuResource) =>
47     (dispatch: Dispatch) => {
48         event.preventDefault();
49         const { left, top } = event.currentTarget.getBoundingClientRect();
50         dispatch(
51             contextMenuActions.OPEN_CONTEXT_MENU({
52                 position: {
53                     x: event.clientX || left,
54                     y: event.clientY || top,
55                 },
56                 resource
57             })
58         );
59     };
60
61 export const openCollectionFilesContextMenu = (event: React.MouseEvent<HTMLElement>, isWritable: boolean) =>
62     (dispatch: Dispatch, getState: () => RootState) => {
63         const isCollectionFileSelected = JSON.stringify(getState().collectionPanelFiles).includes('"selected":true');
64         dispatch<any>(openContextMenu(event, {
65             name: '',
66             uuid: '',
67             ownerUuid: '',
68             description: '',
69             kind: ResourceKind.COLLECTION,
70             menuKind: isCollectionFileSelected
71                 ? isWritable
72                     ? ContextMenuKind.COLLECTION_FILES
73                     : ContextMenuKind.READONLY_COLLECTION_FILES
74                 : ContextMenuKind.COLLECTION_FILES_NOT_SELECTED
75         }));
76     };
77
78 export const openRepositoryContextMenu = (event: React.MouseEvent<HTMLElement>, repository: RepositoryResource) =>
79     (dispatch: Dispatch, getState: () => RootState) => {
80         dispatch<any>(openContextMenu(event, {
81             name: '',
82             uuid: repository.uuid,
83             ownerUuid: repository.ownerUuid,
84             kind: ResourceKind.REPOSITORY,
85             menuKind: ContextMenuKind.REPOSITORY
86         }));
87     };
88
89 export const openVirtualMachinesContextMenu = (event: React.MouseEvent<HTMLElement>, repository: VirtualMachinesResource) =>
90     (dispatch: Dispatch, getState: () => RootState) => {
91         dispatch<any>(openContextMenu(event, {
92             name: '',
93             uuid: repository.uuid,
94             ownerUuid: repository.ownerUuid,
95             kind: ResourceKind.VIRTUAL_MACHINE,
96             menuKind: ContextMenuKind.VIRTUAL_MACHINE
97         }));
98     };
99
100 export const openSshKeyContextMenu = (event: React.MouseEvent<HTMLElement>, sshKey: SshKeyResource) =>
101     (dispatch: Dispatch) => {
102         dispatch<any>(openContextMenu(event, {
103             name: '',
104             uuid: sshKey.uuid,
105             ownerUuid: sshKey.ownerUuid,
106             kind: ResourceKind.SSH_KEY,
107             menuKind: ContextMenuKind.SSH_KEY
108         }));
109     };
110
111 export const openKeepServiceContextMenu = (event: React.MouseEvent<HTMLElement>, keepService: KeepServiceResource) =>
112     (dispatch: Dispatch) => {
113         dispatch<any>(openContextMenu(event, {
114             name: '',
115             uuid: keepService.uuid,
116             ownerUuid: keepService.ownerUuid,
117             kind: ResourceKind.KEEP_SERVICE,
118             menuKind: ContextMenuKind.KEEP_SERVICE
119         }));
120     };
121
122 export const openComputeNodeContextMenu = (event: React.MouseEvent<HTMLElement>, resourceUuid: string) =>
123     (dispatch: Dispatch) => {
124         dispatch<any>(openContextMenu(event, {
125             name: '',
126             uuid: resourceUuid,
127             ownerUuid: '',
128             kind: ResourceKind.NODE,
129             menuKind: ContextMenuKind.NODE
130         }));
131     };
132
133 export const openApiClientAuthorizationContextMenu =
134     (event: React.MouseEvent<HTMLElement>, resourceUuid: string) =>
135         (dispatch: Dispatch) => {
136             dispatch<any>(openContextMenu(event, {
137                 name: '',
138                 uuid: resourceUuid,
139                 ownerUuid: '',
140                 kind: ResourceKind.API_CLIENT_AUTHORIZATION,
141                 menuKind: ContextMenuKind.API_CLIENT_AUTHORIZATION
142             }));
143         };
144
145 export const openRootProjectContextMenu = (event: React.MouseEvent<HTMLElement>, projectUuid: string) =>
146     (dispatch: Dispatch, getState: () => RootState) => {
147         const res = getResource<UserResource>(projectUuid)(getState().resources);
148         if (res) {
149             dispatch<any>(openContextMenu(event, {
150                 name: '',
151                 uuid: res.uuid,
152                 ownerUuid: res.uuid,
153                 kind: res.kind,
154                 menuKind: ContextMenuKind.ROOT_PROJECT,
155                 isTrashed: false
156             }));
157         }
158     };
159
160 export const openProjectContextMenu = (event: React.MouseEvent<HTMLElement>, resourceUuid: string) =>
161     (dispatch: Dispatch, getState: () => RootState) => {
162         const res = getResource<GroupContentsResource>(resourceUuid)(getState().resources);
163         const menuKind = dispatch<any>(resourceUuidToContextMenuKind(resourceUuid));
164         if (res && menuKind) {
165             dispatch<any>(openContextMenu(event, {
166                 name: res.name,
167                 uuid: res.uuid,
168                 kind: res.kind,
169                 menuKind,
170                 description: res.description,
171                 ownerUuid: res.ownerUuid,
172                 isTrashed: ('isTrashed' in res) ? res.isTrashed : false,
173             }));
174         }
175     };
176
177 export const openSidePanelContextMenu = (event: React.MouseEvent<HTMLElement>, id: string) =>
178     (dispatch: Dispatch, getState: () => RootState) => {
179         if (!isSidePanelTreeCategory(id)) {
180             const kind = extractUuidKind(id);
181             if (kind === ResourceKind.USER) {
182                 dispatch<any>(openRootProjectContextMenu(event, id));
183             } else if (kind === ResourceKind.PROJECT) {
184                 dispatch<any>(openProjectContextMenu(event, id));
185             }
186         }
187     };
188
189 export const openProcessContextMenu = (event: React.MouseEvent<HTMLElement>, process: Process) =>
190     (dispatch: Dispatch, getState: () => RootState) => {
191         const res = getResource<ProcessResource>(process.containerRequest.uuid)(getState().resources);
192         if (res) {
193             dispatch<any>(openContextMenu(event, {
194                 uuid: res.uuid,
195                 ownerUuid: res.ownerUuid,
196                 kind: ResourceKind.PROCESS,
197                 name: res.name,
198                 description: res.description,
199                 outputUuid: res.outputUuid || '',
200                 workflowUuid: res.properties.workflowUuid || '',
201                 menuKind: ContextMenuKind.PROCESS
202             }));
203         }
204     };
205
206 export const resourceUuidToContextMenuKind = (uuid: string, readonly = false) =>
207     (dispatch: Dispatch, getState: () => RootState) => {
208         const { isAdmin: isAdminUser, uuid: userUuid } = getState().auth.user!;
209         const kind = extractUuidKind(uuid);
210         const resource = getResourceWithEditableStatus<GroupResource & EditableResource>(uuid, userUuid)(getState().resources);
211
212         const isEditable = (isAdminUser || (resource || {} as EditableResource).isEditable) && !readonly;
213         switch (kind) {
214             case ResourceKind.PROJECT:
215                 return (isAdminUser && !readonly)
216                     ? (resource && resource.groupClass !== GroupClass.FILTER)
217                         ? ContextMenuKind.PROJECT_ADMIN
218                         : ContextMenuKind.FILTER_GROUP_ADMIN
219                     : isEditable
220                         ? (resource && resource.groupClass !== GroupClass.FILTER)
221                             ? ContextMenuKind.PROJECT
222                             : ContextMenuKind.FILTER_GROUP
223                         : ContextMenuKind.READONLY_PROJECT;
224             case ResourceKind.COLLECTION:
225                 const c = getResource<CollectionResource>(uuid)(getState().resources);
226                 if (c === undefined) { return; }
227                 const isOldVersion = c.uuid !== c.currentVersionUuid;
228                 const isTrashed = c.isTrashed;
229                 return isOldVersion
230                     ? ContextMenuKind.OLD_VERSION_COLLECTION
231                     : (isTrashed && isEditable)
232                         ? ContextMenuKind.TRASHED_COLLECTION
233                         : (isAdminUser && !readonly)
234                             ? ContextMenuKind.COLLECTION_ADMIN
235                             : isEditable
236                                 ? ContextMenuKind.COLLECTION
237                                 : ContextMenuKind.READONLY_COLLECTION;
238             case ResourceKind.PROCESS:
239                 return (isAdminUser && !readonly)
240                     ? ContextMenuKind.PROCESS_ADMIN
241                     : readonly
242                         ? ContextMenuKind.READONLY_PROCESS_RESOURCE
243                         : ContextMenuKind.PROCESS_RESOURCE;
244             case ResourceKind.USER:
245                 return ContextMenuKind.ROOT_PROJECT;
246             case ResourceKind.LINK:
247                 return ContextMenuKind.LINK;
248             default:
249                 return;
250         }
251     };