From 4aba4d31b34af60e7cb21b1088723aced0699a69 Mon Sep 17 00:00:00 2001 From: Lucas Di Pentima Date: Mon, 23 Nov 2020 19:22:22 -0300 Subject: [PATCH] 17098: Refactors context menu's kind decision function. Over time, the function got several optional arguments that were mostly intrinsic of the resource being queried, or the user type. This produced lot of duplicated and error prone code on the caller's side. Now, all callers only need to provide a uuid, the will get the same result. Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima --- .../context-menu/context-menu-actions.ts | 77 +++++++++++-------- .../collection-content-address-panel.tsx | 24 ++++-- .../collection-panel/collection-panel.tsx | 13 +--- src/views/favorite-panel/favorite-panel.tsx | 16 ++-- src/views/link-panel/link-panel.tsx | 13 +++- src/views/project-panel/project-panel.tsx | 28 ++++--- .../public-favorites-panel.tsx | 9 ++- .../shared-with-me-panel.tsx | 20 ++--- 8 files changed, 117 insertions(+), 83 deletions(-) diff --git a/src/store/context-menu/context-menu-actions.ts b/src/store/context-menu/context-menu-actions.ts index 308d5e88..9a586890 100644 --- a/src/store/context-menu/context-menu-actions.ts +++ b/src/store/context-menu/context-menu-actions.ts @@ -8,7 +8,6 @@ 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 { ProjectResource } from '~/models/project'; import { UserResource } from '~/models/user'; import { isSidePanelTreeCategory } from '~/store/side-panel-tree/side-panel-tree-actions'; import { extractUuidKind, ResourceKind, EditableResource } from '~/models/resource'; @@ -18,6 +17,9 @@ 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 { GroupResource } from '~/models/group'; +import { GroupContentsResource } from '~/services/groups-service/groups-service'; export const contextMenuActions = unionize({ OPEN_CONTEXT_MENU: ofType<{ position: ContextMenuPosition, resource: ContextMenuResource }>(), @@ -156,9 +158,8 @@ export const openRootProjectContextMenu = (event: React.MouseEvent, export const openProjectContextMenu = (event: React.MouseEvent, resourceUuid: string) => (dispatch: Dispatch, getState: () => RootState) => { - const { isAdmin, uuid: userUuid } = getState().auth.user!; - const res = getResourceWithEditableStatus(resourceUuid, userUuid)(getState().resources); - const menuKind = resourceKindToContextMenuKind(resourceUuid, isAdmin, (res || {} as EditableResource).isEditable); + const res = getResource(resourceUuid)(getState().resources); + const menuKind = dispatch(resourceUuidToContextMenuKind(resourceUuid)); if (res && menuKind) { dispatch(openContextMenu(event, { name: res.name, @@ -166,7 +167,7 @@ export const openProjectContextMenu = (event: React.MouseEvent, res kind: res.kind, menuKind, ownerUuid: res.ownerUuid, - isTrashed: res.isTrashed + isTrashed: ('isTrashed' in res) ? res.isTrashed: false, })); } }; @@ -200,30 +201,42 @@ export const openProcessContextMenu = (event: React.MouseEvent, pro } }; -export const resourceKindToContextMenuKind = (uuid: string, isAdmin?: boolean, isEditable?: boolean) => { - const kind = extractUuidKind(uuid); - switch (kind) { - case ResourceKind.PROJECT: - return !isAdmin - ? isEditable - ? ContextMenuKind.PROJECT - : ContextMenuKind.READONLY_PROJECT - : ContextMenuKind.PROJECT_ADMIN; - case ResourceKind.COLLECTION: - return !isAdmin - ? isEditable - ? ContextMenuKind.COLLECTION - : ContextMenuKind.READONLY_COLLECTION - : ContextMenuKind.COLLECTION_ADMIN; - case ResourceKind.PROCESS: - return !isAdmin - ? ContextMenuKind.PROCESS_RESOURCE - : ContextMenuKind.PROCESS_ADMIN; - case ResourceKind.USER: - return ContextMenuKind.ROOT_PROJECT; - case ResourceKind.LINK: - return ContextMenuKind.LINK; - default: - return; - } -}; +export const resourceUuidToContextMenuKind = (uuid: string) => + (dispatch: Dispatch, getState: () => RootState) => { + const { isAdmin, uuid: userUuid } = getState().auth.user!; + const kind = extractUuidKind(uuid); + const resource = getResourceWithEditableStatus(uuid, userUuid)(getState().resources); + const isEditable = (resource || {} as EditableResource).isEditable; + switch (kind) { + case ResourceKind.PROJECT: + return !isAdmin + ? isEditable + ? ContextMenuKind.PROJECT + : ContextMenuKind.READONLY_PROJECT + : ContextMenuKind.PROJECT_ADMIN; + case ResourceKind.COLLECTION: + const c = getResource(uuid)(getState().resources); + if (c === undefined) { return; } + const isOldVersion = c.uuid !== c.currentVersionUuid; + const isTrashed = c.isTrashed; + return (isTrashed && isEditable) + ? ContextMenuKind.TRASHED_COLLECTION + : isOldVersion + ? ContextMenuKind.OLD_VERSION_COLLECTION + : isAdmin + ? ContextMenuKind.COLLECTION_ADMIN + : isEditable + ? ContextMenuKind.COLLECTION + : ContextMenuKind.READONLY_COLLECTION; + case ResourceKind.PROCESS: + return !isAdmin + ? ContextMenuKind.PROCESS_RESOURCE + : ContextMenuKind.PROCESS_ADMIN; + case ResourceKind.USER: + return ContextMenuKind.ROOT_PROJECT; + case ResourceKind.LINK: + return ContextMenuKind.LINK; + default: + return; + } + }; diff --git a/src/views/collection-content-address-panel/collection-content-address-panel.tsx b/src/views/collection-content-address-panel/collection-content-address-panel.tsx index 038fea2f..06ea910d 100644 --- a/src/views/collection-content-address-panel/collection-content-address-panel.tsx +++ b/src/views/collection-content-address-panel/collection-content-address-panel.tsx @@ -3,7 +3,13 @@ // SPDX-License-Identifier: AGPL-3.0 import * as React from 'react'; -import { StyleRulesCallback, WithStyles, withStyles, Grid, Button } from '@material-ui/core'; +import { + StyleRulesCallback, + WithStyles, + withStyles, + Grid, + Button +} from '@material-ui/core'; import { CollectionIcon } from '~/components/icon/icon'; import { ArvadosTheme } from '~/common/custom-theme'; import { BackIcon } from '~/components/icon/icon'; @@ -11,8 +17,10 @@ import { DataTableDefaultView } from '~/components/data-table-default-view/data- import { COLLECTIONS_CONTENT_ADDRESS_PANEL_ID } from '~/store/collections-content-address-panel/collections-content-address-panel-actions'; import { DataExplorer } from "~/views-components/data-explorer/data-explorer"; import { Dispatch } from 'redux'; -import { getIsAdmin } from '~/store/public-favorites/public-favorites-actions'; -import { resourceKindToContextMenuKind, openContextMenu } from '~/store/context-menu/context-menu-actions'; +import { + resourceUuidToContextMenuKind, + openContextMenu +} from '~/store/context-menu/context-menu-actions'; import { ResourceKind } from '~/models/resource'; import { loadDetailsPanel } from '~/store/details-panel/details-panel-action'; import { connect } from 'react-redux'; @@ -20,7 +28,12 @@ import { navigateTo } from '~/store/navigation/navigation-action'; import { DataColumns } from '~/components/data-table/data-table'; import { SortDirection } from '~/components/data-table/data-column'; import { createTree } from '~/models/tree'; -import { ResourceName, ResourceOwnerName, ResourceLastModifiedDate, ResourceStatus } from '~/views-components/data-explorer/renderers'; +import { + ResourceName, + ResourceOwnerName, + ResourceLastModifiedDate, + ResourceStatus +} from '~/views-components/data-explorer/renderers'; type CssRules = 'backLink' | 'backIcon' | 'card' | 'title' | 'iconHeader' | 'link'; @@ -105,8 +118,7 @@ export interface CollectionContentAddressPanelActionProps { const mapDispatchToProps = (dispatch: Dispatch): CollectionContentAddressPanelActionProps => ({ onContextMenu: (event, resourceUuid) => { - const isAdmin = dispatch(getIsAdmin()); - const kind = resourceKindToContextMenuKind(resourceUuid, isAdmin); + const kind = dispatch(resourceUuidToContextMenuKind(resourceUuid)); if (kind) { dispatch(openContextMenu(event, { name: '', diff --git a/src/views/collection-panel/collection-panel.tsx b/src/views/collection-panel/collection-panel.tsx index b7bd3a62..685bb78b 100644 --- a/src/views/collection-panel/collection-panel.tsx +++ b/src/views/collection-panel/collection-panel.tsx @@ -19,8 +19,7 @@ import { CollectionPanelFiles } from '~/views-components/collection-panel-files/ import { CollectionTagForm } from './collection-tag-form'; import { deleteCollectionTag, navigateToProcess, collectionPanelActions } from '~/store/collection-panel/collection-panel-action'; import { getResource } from '~/store/resources/resources'; -import { openContextMenu } from '~/store/context-menu/context-menu-actions'; -import { ContextMenuKind } from '~/views-components/context-menu/context-menu'; +import { openContextMenu, resourceUuidToContextMenuKind } from '~/store/context-menu/context-menu-actions'; import { formatDate, formatFileSize } from "~/common/formatters"; import { openDetailsPanel } from '~/store/details-panel/details-panel-action'; import { snackbarActions, SnackbarKind } from '~/store/snackbar/snackbar-actions'; @@ -237,19 +236,15 @@ export const CollectionPanel = withStyles(styles)( } handleContextMenu = (event: React.MouseEvent) => { - const { uuid, ownerUuid, name, description, kind, isTrashed } = this.props.item; - const { isWritable } = this.props; + const { uuid, ownerUuid, name, description, kind } = this.props.item; + const menuKind = this.props.dispatch(resourceUuidToContextMenuKind(uuid)); const resource = { uuid, ownerUuid, name, description, kind, - menuKind: isWritable - ? isTrashed - ? ContextMenuKind.TRASHED_COLLECTION - : ContextMenuKind.COLLECTION - : ContextMenuKind.READONLY_COLLECTION + menuKind, }; // Avoid expanding/collapsing the panel event.stopPropagation(); diff --git a/src/views/favorite-panel/favorite-panel.tsx b/src/views/favorite-panel/favorite-panel.tsx index cad2f9ba..48a9e330 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, EditableResource } from '~/models/resource'; +import { ResourceKind } from '~/models/resource'; import { ArvadosTheme } from '~/common/custom-theme'; import { FAVORITE_PANEL_ID } from "~/store/favorite-panel/favorite-panel-action"; import { @@ -22,7 +22,10 @@ import { ResourceType } from '~/views-components/data-explorer/renderers'; import { FavoriteIcon } from '~/components/icon/icon'; -import { openContextMenu, resourceKindToContextMenuKind } from '~/store/context-menu/context-menu-actions'; +import { + openContextMenu, + resourceUuidToContextMenuKind +} from '~/store/context-menu/context-menu-actions'; import { loadDetailsPanel } from '~/store/details-panel/details-panel-action'; import { navigateTo } from '~/store/navigation/navigation-action'; import { ContainerRequestState } from "~/models/container-request"; @@ -31,8 +34,7 @@ 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'; +import { ResourcesState } from '~/store/resources/resources'; type CssRules = "toolbar" | "button"; @@ -109,7 +111,6 @@ export const favoritePanelColumns: DataColumns = [ interface FavoritePanelDataProps { favorites: FavoritesState; resources: ResourcesState; - isAdmin: boolean; userUuid: string; } @@ -121,7 +122,6 @@ interface FavoritePanelActionProps { const mapStateToProps = (state : RootState): FavoritePanelDataProps => ({ favorites: state.favorites, resources: state.resources, - isAdmin: state.auth.user!.isAdmin, userUuid: state.auth.user!.uuid, }); @@ -133,9 +133,7 @@ export const FavoritePanel = withStyles(styles)( class extends React.Component { handleContextMenu = (event: React.MouseEvent, resourceUuid: string) => { - const { isAdmin, userUuid, resources } = this.props; - const resource = getResourceWithEditableStatus(resourceUuid, userUuid)(resources); - const menuKind = resourceKindToContextMenuKind(resourceUuid, isAdmin, (resource || {} as EditableResource).isEditable); + const menuKind = this.props.dispatch(resourceUuidToContextMenuKind(resourceUuid)); if (menuKind) { this.props.dispatch(openContextMenu(event, { name: '', diff --git a/src/views/link-panel/link-panel.tsx b/src/views/link-panel/link-panel.tsx index 4bff4ee7..f9ec763b 100644 --- a/src/views/link-panel/link-panel.tsx +++ b/src/views/link-panel/link-panel.tsx @@ -5,8 +5,15 @@ import { Dispatch } from "redux"; import { connect } from "react-redux"; import { RootState } from '~/store/store'; -import { openContextMenu, resourceKindToContextMenuKind } from '~/store/context-menu/context-menu-actions'; -import { LinkPanelRoot, LinkPanelRootActionProps, LinkPanelRootDataProps } from '~/views/link-panel/link-panel-root'; +import { + openContextMenu, + resourceUuidToContextMenuKind +} from '~/store/context-menu/context-menu-actions'; +import { + LinkPanelRoot, + LinkPanelRootActionProps, + LinkPanelRootDataProps +} from '~/views/link-panel/link-panel-root'; import { ResourceKind } from '~/models/resource'; const mapStateToProps = (state: RootState): LinkPanelRootDataProps => { @@ -17,7 +24,7 @@ const mapStateToProps = (state: RootState): LinkPanelRootDataProps => { const mapDispatchToProps = (dispatch: Dispatch): LinkPanelRootActionProps => ({ onContextMenu: (event, resourceUuid) => { - const kind = resourceKindToContextMenuKind(resourceUuid); + const kind = dispatch(resourceUuidToContextMenuKind(resourceUuid)); if (kind) { dispatch(openContextMenu(event, { name: '', diff --git a/src/views/project-panel/project-panel.tsx b/src/views/project-panel/project-panel.tsx index 11223f22..47dbd9b0 100644 --- a/src/views/project-panel/project-panel.tsx +++ b/src/views/project-panel/project-panel.tsx @@ -14,7 +14,7 @@ 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, EditableResource } from '~/models/resource'; +import { ResourceKind, Resource } from '~/models/resource'; import { ResourceFileSize, ResourceLastModifiedDate, @@ -24,17 +24,26 @@ import { } from '~/views-components/data-explorer/renderers'; import { ProjectIcon } from '~/components/icon/icon'; import { ResourceName } from '~/views-components/data-explorer/renderers'; -import { ResourcesState, getResourceWithEditableStatus } from '~/store/resources/resources'; +import { + ResourcesState, + getResource +} 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'; +import { + openContextMenu, + resourceUuidToContextMenuKind +} from '~/store/context-menu/context-menu-actions'; import { navigateTo } from '~/store/navigation/navigation-action'; import { getProperty } from '~/store/properties/properties'; import { PROJECT_PANEL_CURRENT_UUID } from '~/store/project-panel/project-panel-action'; import { DataTableDefaultView } from '~/components/data-table-default-view/data-table-default-view'; import { ArvadosTheme } from "~/common/custom-theme"; import { createTree } from '~/models/tree'; -import { getInitialResourceTypeFilters, getInitialProcessStatusFilters } from '~/store/resource-type-filters/resource-type-filters'; +import { + getInitialResourceTypeFilters, + getInitialProcessStatusFilters +} from '~/store/resource-type-filters/resource-type-filters'; +import { GroupContentsResource } from '~/services/groups-service/groups-service'; type CssRules = 'root' | "button"; @@ -131,7 +140,6 @@ export const ProjectPanel = withStyles(styles)( connect((state: RootState) => ({ currentItemId: getProperty(PROJECT_PANEL_CURRENT_UUID)(state.properties), resources: state.resources, - isAdmin: state.auth.user!.isAdmin, userUuid: state.auth.user!.uuid, }))( class extends React.Component { @@ -157,15 +165,15 @@ export const ProjectPanel = withStyles(styles)( } handleContextMenu = (event: React.MouseEvent, resourceUuid: string) => { - const { isAdmin, userUuid, resources } = this.props; - const resource = getResourceWithEditableStatus(resourceUuid, userUuid)(resources); - const menuKind = resourceKindToContextMenuKind(resourceUuid, isAdmin, (resource || {} as EditableResource).isEditable); + const { resources } = this.props; + const resource = getResource(resourceUuid)(resources); + const menuKind = this.props.dispatch(resourceUuidToContextMenuKind(resourceUuid)); if (menuKind && resource) { this.props.dispatch(openContextMenu(event, { name: resource.name, uuid: resource.uuid, ownerUuid: resource.ownerUuid, - isTrashed: resource.isTrashed, + isTrashed: ('isTrashed' in resource) ? resource.isTrashed: false, kind: resource.kind, menuKind })); diff --git a/src/views/public-favorites-panel/public-favorites-panel.tsx b/src/views/public-favorites-panel/public-favorites-panel.tsx index 635ac621..800e5e59 100644 --- a/src/views/public-favorites-panel/public-favorites-panel.tsx +++ b/src/views/public-favorites-panel/public-favorites-panel.tsx @@ -22,7 +22,10 @@ import { } from '~/views-components/data-explorer/renderers'; import { PublicFavoriteIcon } from '~/components/icon/icon'; import { Dispatch } from 'redux'; -import { openContextMenu, resourceKindToContextMenuKind } from '~/store/context-menu/context-menu-actions'; +import { + openContextMenu, + resourceUuidToContextMenuKind +} from '~/store/context-menu/context-menu-actions'; import { loadDetailsPanel } from '~/store/details-panel/details-panel-action'; import { navigateTo } from '~/store/navigation/navigation-action'; import { ContainerRequestState } from "~/models/container-request"; @@ -32,7 +35,6 @@ import { createTree } from '~/models/tree'; import { getSimpleObjectTypeFilters } from '~/store/resource-type-filters/resource-type-filters'; import { PUBLIC_FAVORITE_PANEL_ID } from '~/store/public-favorites-panel/public-favorites-action'; import { PublicFavoritesState } from '~/store/public-favorites/public-favorites-reducer'; -import { getIsAdmin } from '~/store/public-favorites/public-favorites-actions'; type CssRules = "toolbar" | "button"; @@ -122,8 +124,7 @@ const mapStateToProps = ({ publicFavorites }: RootState): PublicFavoritePanelDat const mapDispatchToProps = (dispatch: Dispatch): PublicFavoritePanelActionProps => ({ onContextMenu: (event, resourceUuid) => { - const isAdmin = dispatch(getIsAdmin()); - const kind = resourceKindToContextMenuKind(resourceUuid, isAdmin); + const kind = dispatch(resourceUuidToContextMenuKind(resourceUuid)); if (kind) { dispatch(openContextMenu(event, { 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 9b4bcc85..76a314ae 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,14 +9,16 @@ 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, getResourceWithEditableStatus } from '~/store/resources/resources'; +import { ResourcesState, getResource } 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'; +import { + openContextMenu, + resourceUuidToContextMenuKind +} from '~/store/context-menu/context-menu-actions'; +import { GroupContentsResource } from '~/services/groups-service/groups-service'; type CssRules = "toolbar" | "button"; @@ -32,7 +34,6 @@ const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ interface SharedWithMePanelDataProps { resources: ResourcesState; - isAdmin: boolean; userUuid: string; } @@ -41,7 +42,6 @@ type SharedWithMePanelProps = SharedWithMePanelDataProps & DispatchProp & WithSt export const SharedWithMePanel = withStyles(styles)( connect((state: RootState) => ({ resources: state.resources, - isAdmin: state.auth.user!.isAdmin, userUuid: state.auth.user!.uuid, }))( class extends React.Component { @@ -56,15 +56,15 @@ export const SharedWithMePanel = withStyles(styles)( } handleContextMenu = (event: React.MouseEvent, resourceUuid: string) => { - const { isAdmin, userUuid, resources } = this.props; - const resource = getResourceWithEditableStatus(resourceUuid, userUuid)(resources); - const menuKind = resourceKindToContextMenuKind(resourceUuid, isAdmin, (resource || {} as EditableResource).isEditable); + const { resources } = this.props; + const resource = getResource(resourceUuid)(resources); + const menuKind = this.props.dispatch(resourceUuidToContextMenuKind(resourceUuid)); if (menuKind && resource) { this.props.dispatch(openContextMenu(event, { name: '', uuid: resource.uuid, ownerUuid: resource.ownerUuid, - isTrashed: resource.isTrashed, + isTrashed: ('isTrashed' in resource) ? resource.isTrashed: false, kind: resource.kind, menuKind })); -- 2.30.2