X-Git-Url: https://git.arvados.org/arvados-workbench2.git/blobdiff_plain/78224728bc808a560718e934ef124afa77b81834..44821dc84765936ce99fa2d760ef683281f0a578:/src/store/context-menu/context-menu-actions.ts diff --git a/src/store/context-menu/context-menu-actions.ts b/src/store/context-menu/context-menu-actions.ts index ca89f3eb..6fc5d56f 100644 --- a/src/store/context-menu/context-menu-actions.ts +++ b/src/store/context-menu/context-menu-actions.ts @@ -2,27 +2,31 @@ // // 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 } 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 { 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 { ApiClientAuthorization } from '~/models/api-client-authorization'; -import { ProcessResource } from '~/models/process'; +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"; 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; @@ -32,180 +36,279 @@ export type ContextMenuResource = { uuid: string; ownerUuid: string; description?: string; - kind: ResourceKind, - menuKind: ContextMenuKind; + kind: ResourceKind; + menuKind: ContextMenuKind | string; isTrashed?: boolean; + isEditable?: boolean; outputUuid?: string; + workflowUuid?: string; + isAdmin?: boolean; + isFrozen?: boolean; + storageClassesDesired?: string[]; + properties?: { [key: string]: string | string[] }; + isMulti?: boolean; + isSingle?: boolean; }; export const isKeyboardClick = (event: React.MouseEvent) => event.nativeEvent.detail === 0; -export const openContextMenu = (event: React.MouseEvent, resource: ContextMenuResource) => - (dispatch: Dispatch) => { - event.preventDefault(); - const { left, top } = event.currentTarget.getBoundingClientRect(); - dispatch( - contextMenuActions.OPEN_CONTEXT_MENU({ - position: { - x: event.clientX || left, - y: event.clientY || top, - }, - resource - }) - ); - }; +export const openContextMenu = (event: React.MouseEvent, resource: ContextMenuResource) => (dispatch: Dispatch) => { + event.preventDefault(); + const { left, top } = event.currentTarget.getBoundingClientRect(); + dispatch( + contextMenuActions.OPEN_CONTEXT_MENU({ + position: { + x: event.clientX || left, + y: event.clientY || top, + }, + resource, + }) + ); +}; -export const openCollectionFilesContextMenu = (event: React.MouseEvent) => - (dispatch: Dispatch, getState: () => RootState) => { +export const openCollectionFilesContextMenu = + (event: React.MouseEvent, isWritable: boolean) => (dispatch: Dispatch, getState: () => RootState) => { const isCollectionFileSelected = JSON.stringify(getState().collectionPanelFiles).includes('"selected":true'); - dispatch(openContextMenu(event, { - name: '', - uuid: '', - ownerUuid: '', - kind: ResourceKind.COLLECTION, - menuKind: isCollectionFileSelected ? ContextMenuKind.COLLECTION_FILES : ContextMenuKind.COLLECTION_FILES_NOT_SELECTED - })); + dispatch( + openContextMenu(event, { + name: "", + uuid: "", + ownerUuid: "", + description: "", + kind: ResourceKind.COLLECTION, + menuKind: isCollectionFileSelected + ? isWritable + ? ContextMenuKind.COLLECTION_FILES + : ContextMenuKind.READONLY_COLLECTION_FILES + : ContextMenuKind.COLLECTION_FILES_NOT_SELECTED, + }) + ); }; -export const openRepositoryContextMenu = (event: React.MouseEvent, repository: RepositoryResource) => - (dispatch: Dispatch, getState: () => RootState) => { - dispatch(openContextMenu(event, { - name: '', - uuid: repository.uuid, - ownerUuid: repository.ownerUuid, - kind: ResourceKind.REPOSITORY, - menuKind: ContextMenuKind.REPOSITORY - })); +export const openRepositoryContextMenu = + (event: React.MouseEvent, repository: RepositoryResource) => (dispatch: Dispatch, getState: () => RootState) => { + dispatch( + openContextMenu(event, { + name: "", + uuid: repository.uuid, + ownerUuid: repository.ownerUuid, + kind: ResourceKind.REPOSITORY, + menuKind: ContextMenuKind.REPOSITORY, + }) + ); }; -export const openVirtualMachinesContextMenu = (event: React.MouseEvent, repository: VirtualMachinesResource) => - (dispatch: Dispatch, getState: () => RootState) => { - dispatch(openContextMenu(event, { - name: '', - uuid: repository.uuid, - ownerUuid: repository.ownerUuid, - kind: ResourceKind.VIRTUAL_MACHINE, - menuKind: ContextMenuKind.VIRTUAL_MACHINE - })); +export const openVirtualMachinesContextMenu = + (event: React.MouseEvent, repository: VirtualMachinesResource) => (dispatch: Dispatch, getState: () => RootState) => { + dispatch( + openContextMenu(event, { + name: "", + uuid: repository.uuid, + ownerUuid: repository.ownerUuid, + kind: ResourceKind.VIRTUAL_MACHINE, + menuKind: ContextMenuKind.VIRTUAL_MACHINE, + }) + ); }; -export const openSshKeyContextMenu = (event: React.MouseEvent, sshKey: SshKeyResource) => - (dispatch: Dispatch) => { - dispatch(openContextMenu(event, { - name: '', +export const openSshKeyContextMenu = (event: React.MouseEvent, sshKey: SshKeyResource) => (dispatch: Dispatch) => { + dispatch( + openContextMenu(event, { + name: "", uuid: sshKey.uuid, ownerUuid: sshKey.ownerUuid, kind: ResourceKind.SSH_KEY, - menuKind: ContextMenuKind.SSH_KEY - })); - }; + menuKind: ContextMenuKind.SSH_KEY, + }) + ); +}; -export const openKeepServiceContextMenu = (event: React.MouseEvent, keepService: KeepServiceResource) => - (dispatch: Dispatch) => { - dispatch(openContextMenu(event, { - name: '', +export const openKeepServiceContextMenu = (event: React.MouseEvent, keepService: KeepServiceResource) => (dispatch: Dispatch) => { + dispatch( + openContextMenu(event, { + name: "", uuid: keepService.uuid, ownerUuid: keepService.ownerUuid, kind: ResourceKind.KEEP_SERVICE, - menuKind: ContextMenuKind.KEEP_SERVICE - })); - }; + menuKind: ContextMenuKind.KEEP_SERVICE, + }) + ); +}; -export const openComputeNodeContextMenu = (event: React.MouseEvent, resourceUuid: string) => - (dispatch: Dispatch) => { - dispatch(openContextMenu(event, { - name: '', +export const openApiClientAuthorizationContextMenu = (event: React.MouseEvent, resourceUuid: string) => (dispatch: Dispatch) => { + dispatch( + openContextMenu(event, { + name: "", uuid: resourceUuid, - ownerUuid: '', - kind: ResourceKind.NODE, - menuKind: ContextMenuKind.NODE - })); - }; + ownerUuid: "", + kind: ResourceKind.API_CLIENT_AUTHORIZATION, + menuKind: ContextMenuKind.API_CLIENT_AUTHORIZATION, + }) + ); +}; -export const openApiClientAuthorizationContextMenu = - (event: React.MouseEvent, resourceUuid: string) => - (dispatch: Dispatch) => { - dispatch(openContextMenu(event, { - name: '', - uuid: resourceUuid, - ownerUuid: '', - kind: ResourceKind.API_CLIENT_AUTHORIZATION, - menuKind: ContextMenuKind.API_CLIENT_AUTHORIZATION - })); - }; - -export const openRootProjectContextMenu = (event: React.MouseEvent, projectUuid: string) => - (dispatch: Dispatch, getState: () => RootState) => { +export const openRootProjectContextMenu = + (event: React.MouseEvent, projectUuid: string) => (dispatch: Dispatch, getState: () => RootState) => { const res = getResource(projectUuid)(getState().resources); if (res) { - dispatch(openContextMenu(event, { - name: '', - uuid: res.uuid, - ownerUuid: res.uuid, - kind: res.kind, - menuKind: ContextMenuKind.ROOT_PROJECT, - isTrashed: false - })); + dispatch( + openContextMenu(event, { + name: "", + uuid: res.uuid, + ownerUuid: res.uuid, + kind: res.kind, + menuKind: ContextMenuKind.ROOT_PROJECT, + isTrashed: false, + }) + ); } }; -export const openProjectContextMenu = (event: React.MouseEvent, projectUuid: string) => - (dispatch: Dispatch, getState: () => RootState) => { - const res = getResource(projectUuid)(getState().resources); - if (res) { - dispatch(openContextMenu(event, { - name: res.name, - uuid: res.uuid, - kind: res.kind, - menuKind: ContextMenuKind.PROJECT, - ownerUuid: res.ownerUuid, - isTrashed: res.isTrashed - })); +export const openProjectContextMenu = + (event: React.MouseEvent, resourceUuid: string) => (dispatch: Dispatch, getState: () => RootState) => { + const res = getResource(resourceUuid)(getState().resources); + const menuKind = dispatch(resourceUuidToContextMenuKind(resourceUuid)); + if (res && menuKind) { + dispatch( + openContextMenu(event, { + name: res.name, + uuid: res.uuid, + kind: res.kind, + menuKind, + description: res.description, + ownerUuid: res.ownerUuid, + isTrashed: "isTrashed" in res ? res.isTrashed : false, + isFrozen: !!(res as ProjectResource).frozenByUuid, + }) + ); } }; -export const openSidePanelContextMenu = (event: React.MouseEvent, id: string) => - (dispatch: Dispatch, getState: () => RootState) => { - if (!isSidePanelTreeCategory(id)) { - const kind = extractUuidKind(id); - if (kind === ResourceKind.USER) { - dispatch(openRootProjectContextMenu(event, id)); - } else if (kind === ResourceKind.PROJECT) { - dispatch(openProjectContextMenu(event, id)); - } +export const openSidePanelContextMenu = (event: React.MouseEvent, id: string) => (dispatch: Dispatch, getState: () => RootState) => { + if (!isSidePanelTreeCategory(id)) { + const kind = extractUuidKind(id); + if (kind === ResourceKind.USER) { + dispatch(openRootProjectContextMenu(event, id)); + } else if (kind === ResourceKind.PROJECT) { + dispatch(openProjectContextMenu(event, id)); } - }; + } +}; -export const openProcessContextMenu = (event: React.MouseEvent, process: Process) => - (dispatch: Dispatch, getState: () => RootState) => { - const res = getResource(process.containerRequest.uuid)(getState().resources); - if (res) { - dispatch(openContextMenu(event, { +export const openProcessContextMenu = (event: React.MouseEvent, process: Process) => (dispatch: Dispatch, getState: () => RootState) => { + const res = getResource(process.containerRequest.uuid)(getState().resources); + if (res) { + dispatch( + openContextMenu(event, { uuid: res.uuid, ownerUuid: res.ownerUuid, kind: ResourceKind.PROCESS, name: res.name, description: res.description, - outputUuid: res.outputUuid || '', - menuKind: ContextMenuKind.PROCESS - })); + outputUuid: res.outputUuid || "", + workflowUuid: res.properties.template_uuid || "", + menuKind: ContextMenuKind.PROCESS_RESOURCE, + }) + ); + } +}; + +export const openPermissionEditContextMenu = + (event: React.MouseEvent, link: LinkResource) => (dispatch: Dispatch, getState: () => RootState) => { + if (link) { + dispatch( + openContextMenu(event, { + name: link.name, + uuid: link.uuid, + kind: link.kind, + menuKind: ContextMenuKind.PERMISSION_EDIT, + ownerUuid: link.ownerUuid, + }) + ); } }; -export const resourceKindToContextMenuKind = (uuid: string) => { - const kind = extractUuidKind(uuid); - switch (kind) { - case ResourceKind.PROJECT: - return ContextMenuKind.PROJECT; - case ResourceKind.COLLECTION: - return ContextMenuKind.COLLECTION_RESOURCE; - case ResourceKind.PROCESS: - return ContextMenuKind.PROCESS_RESOURCE; - case ResourceKind.USER: - return ContextMenuKind.ROOT_PROJECT; - case ResourceKind.LINK: - return ContextMenuKind.LINK; - default: - return; - } +export const openUserContextMenu = (event: React.MouseEvent, user: UserResource) => (dispatch: Dispatch, getState: () => RootState) => { + dispatch( + openContextMenu(event, { + name: "", + uuid: user.uuid, + ownerUuid: user.ownerUuid, + kind: user.kind, + menuKind: ContextMenuKind.USER, + }) + ); }; + +export const resourceUuidToContextMenuKind = + (uuid: string, readonly = false) => + (dispatch: Dispatch, getState: () => RootState) => { + const { isAdmin: isAdminUser, uuid: userUuid } = getState().auth.user!; + const kind = extractUuidKind(uuid); + const resource = getResourceWithEditableStatus(uuid, userUuid)(getState().resources); + const isFrozen = resourceIsFrozen(resource, getState().resources); + const isEditable = (isAdminUser || (resource || ({} as EditableResource)).isEditable) && !readonly && !isFrozen; + + switch (kind) { + case ResourceKind.PROJECT: + if (isFrozen) { + return isAdminUser ? ContextMenuKind.FROZEN_PROJECT_ADMIN : ContextMenuKind.FROZEN_PROJECT; + } + + return isAdminUser && !readonly + ? resource && resource.groupClass !== GroupClass.FILTER + ? ContextMenuKind.PROJECT_ADMIN + : ContextMenuKind.FILTER_GROUP_ADMIN + : isEditable + ? resource && resource.groupClass !== GroupClass.FILTER + ? ContextMenuKind.PROJECT + : ContextMenuKind.FILTER_GROUP + : ContextMenuKind.READONLY_PROJECT; + case ResourceKind.COLLECTION: + const c = getResource(uuid)(getState().resources); + if (c === undefined) { + return; + } + const isOldVersion = c.uuid !== c.currentVersionUuid; + const isTrashed = c.isTrashed; + return isOldVersion + ? ContextMenuKind.OLD_VERSION_COLLECTION + : isTrashed && isEditable + ? ContextMenuKind.TRASHED_COLLECTION + : isAdminUser && isEditable + ? ContextMenuKind.COLLECTION_ADMIN + : isEditable + ? ContextMenuKind.COLLECTION + : ContextMenuKind.READONLY_COLLECTION; + case ResourceKind.PROCESS: + return isAdminUser && isEditable + ? ContextMenuKind.PROCESS_ADMIN + : readonly + ? ContextMenuKind.READONLY_PROCESS_RESOURCE + : ContextMenuKind.PROCESS_RESOURCE; + case ResourceKind.USER: + return ContextMenuKind.ROOT_PROJECT; + case ResourceKind.LINK: + return ContextMenuKind.LINK; + case ResourceKind.WORKFLOW: + return ContextMenuKind.WORKFLOW; + default: + return; + } + }; + +export const openSearchResultsContextMenu = + (event: React.MouseEvent, uuid: string) => (dispatch: Dispatch, getState: () => RootState) => { + const res = getResource(uuid)(getState().resources); + if (res) { + dispatch( + openContextMenu(event, { + name: "", + uuid: res.uuid, + ownerUuid: "", + kind: res.kind, + menuKind: ContextMenuKind.SEARCH_RESULTS, + }) + ); + } + };