From 4afdbe4d6f060423725d77fa7500d76d8e7e5f04 Mon Sep 17 00:00:00 2001 From: Pawel Kowalczyk Date: Tue, 11 Dec 2018 11:18:01 +0100 Subject: [PATCH] context menu for groups panel Feature #14505 Arvados-DCO-1.1-Signed-off-by: Pawel Kowalczyk --- src/index.tsx | 2 + .../groups-panel/groups-panel-actions.ts | 35 ++++++ src/store/users/users-actions.ts | 3 +- .../action-sets/group-action-set.ts | 28 +++++ .../context-menu/context-menu.tsx | 3 +- .../groups-dialog/attributes-dialog.tsx | 101 ++++++++++++++++++ .../groups-dialog/remove-dialog.ts | 21 ++++ src/views/groups-panel/groups-panel.tsx | 39 ++++++- src/views/workbench/workbench.tsx | 4 + 9 files changed, 228 insertions(+), 8 deletions(-) create mode 100644 src/views-components/context-menu/action-sets/group-action-set.ts create mode 100644 src/views-components/groups-dialog/attributes-dialog.tsx create mode 100644 src/views-components/groups-dialog/remove-dialog.ts diff --git a/src/index.tsx b/src/index.tsx index 8f702af1..13598dac 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -56,6 +56,7 @@ import { virtualMachineActionSet } from '~/views-components/context-menu/action- import { userActionSet } from '~/views-components/context-menu/action-sets/user-action-set'; import { computeNodeActionSet } from '~/views-components/context-menu/action-sets/compute-node-action-set'; import { apiClientAuthorizationActionSet } from '~/views-components/context-menu/action-sets/api-client-authorization-action-set'; +import { groupActionSet } from '~/views-components/context-menu/action-sets/group-action-set'; console.log(`Starting arvados [${getBuildInfo()}]`); @@ -79,6 +80,7 @@ addMenuActionSet(ContextMenuKind.KEEP_SERVICE, keepServiceActionSet); addMenuActionSet(ContextMenuKind.USER, userActionSet); addMenuActionSet(ContextMenuKind.NODE, computeNodeActionSet); addMenuActionSet(ContextMenuKind.API_CLIENT_AUTHORIZATION, apiClientAuthorizationActionSet); +addMenuActionSet(ContextMenuKind.GROUPS, groupActionSet); fetchConfig() .then(({ config, apiHost }) => { diff --git a/src/store/groups-panel/groups-panel-actions.ts b/src/store/groups-panel/groups-panel-actions.ts index b5465d0d..e46ebd1f 100644 --- a/src/store/groups-panel/groups-panel-actions.ts +++ b/src/store/groups-panel/groups-panel-actions.ts @@ -7,12 +7,19 @@ import { reset } from 'redux-form'; import { bindDataExplorerActions } from "~/store/data-explorer/data-explorer-action"; import { dialogActions } from '~/store/dialog/dialog-actions'; import { Person } from '~/views-components/sharing-dialog/people-select'; +import { RootState } from '~/store/store'; +import { ServiceRepository } from '~/services/services'; +import { getResource } from '~/store/resources/resources'; +import { GroupResource } from '~/models/group'; +import { snackbarActions, SnackbarKind } from '~/store/snackbar/snackbar-actions'; export const GROUPS_PANEL_ID = "groupsPanel"; export const CREATE_GROUP_DIALOG = "createGroupDialog"; export const CREATE_GROUP_FORM = "createGroupForm"; export const CREATE_GROUP_NAME_FIELD_NAME = 'name'; export const CREATE_GROUP_USERS_FIELD_NAME = 'users'; +export const GROUP_ATTRIBUTES_DIALOG = 'groupAttributesDialog'; +export const GROUP_REMOVE_DIALOG = 'groupRemoveDialog'; export const GroupsPanelActions = bindDataExplorerActions(GROUPS_PANEL_ID); @@ -24,6 +31,34 @@ export const openCreateGroupDialog = () => dispatch(reset(CREATE_GROUP_FORM)); }; +export const openGroupAttributes = (uuid: string) => + (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + const { resources } = getState(); + const data = getResource(uuid)(resources); + dispatch(dialogActions.OPEN_DIALOG({ id: GROUP_ATTRIBUTES_DIALOG, data })); + }; + +export const removeGroup = (uuid: string) => + async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removing ...' })); + await services.groupsService.delete(uuid); + dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removed.', hideDuration: 2000, kind: SnackbarKind.SUCCESS })); + dispatch(loadGroupsPanel()); + }; + +export const openRemoveGroupDialog = (uuid: string) => + (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + dispatch(dialogActions.OPEN_DIALOG({ + id: GROUP_REMOVE_DIALOG, + data: { + title: 'Remove group', + text: 'Are you sure you want to remove this group?', + confirmButtonLabel: 'Remove', + uuid + } + })); + }; + export interface CreateGroupFormData { [CREATE_GROUP_NAME_FIELD_NAME]: string; [CREATE_GROUP_USERS_FIELD_NAME]: Person[]; diff --git a/src/store/users/users-actions.ts b/src/store/users/users-actions.ts index 64c3646a..f431fc7f 100644 --- a/src/store/users/users-actions.ts +++ b/src/store/users/users-actions.ts @@ -7,8 +7,7 @@ import { bindDataExplorerActions } from '~/store/data-explorer/data-explorer-act import { RootState } from '~/store/store'; import { ServiceRepository } from "~/services/services"; import { dialogActions } from '~/store/dialog/dialog-actions'; -import { startSubmit, reset, stopSubmit } from "redux-form"; -import { getCommonResourceServiceError, CommonResourceServiceError } from "~/services/common-service/common-resource-service"; +import { startSubmit, reset } from "redux-form"; import { snackbarActions, SnackbarKind } from '~/store/snackbar/snackbar-actions'; import { UserResource } from "~/models/user"; import { getResource } from '~/store/resources/resources'; diff --git a/src/views-components/context-menu/action-sets/group-action-set.ts b/src/views-components/context-menu/action-sets/group-action-set.ts new file mode 100644 index 00000000..8d718a33 --- /dev/null +++ b/src/views-components/context-menu/action-sets/group-action-set.ts @@ -0,0 +1,28 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { ContextMenuActionSet } from "~/views-components/context-menu/context-menu-action-set"; +import { AdvancedIcon, RemoveIcon, AttributesIcon } from "~/components/icon/icon"; +import { openAdvancedTabDialog } from "~/store/advanced-tab/advanced-tab"; +import { openGroupAttributes, openRemoveGroupDialog } from "~/store/groups-panel/groups-panel-actions"; + +export const groupActionSet: ContextMenuActionSet = [[{ + name: "Attributes", + icon: AttributesIcon, + execute: (dispatch, { uuid }) => { + dispatch(openGroupAttributes(uuid)); + } +}, { + name: "Advanced", + icon: AdvancedIcon, + execute: (dispatch, resource) => { + dispatch(openAdvancedTabDialog(resource.uuid)); + } +}, { + name: "Remove", + icon: RemoveIcon, + execute: (dispatch, { uuid }) => { + dispatch(openRemoveGroupDialog(uuid)); + } +}]]; \ No newline at end of file diff --git a/src/views-components/context-menu/context-menu.tsx b/src/views-components/context-menu/context-menu.tsx index 95a4a83f..99a595e0 100644 --- a/src/views-components/context-menu/context-menu.tsx +++ b/src/views-components/context-menu/context-menu.tsx @@ -75,5 +75,6 @@ export enum ContextMenuKind { VIRTUAL_MACHINE = "VirtualMachine", KEEP_SERVICE = "KeepService", USER = "User", - NODE = "Node" + NODE = "Node", + GROUPS = "Group" } diff --git a/src/views-components/groups-dialog/attributes-dialog.tsx b/src/views-components/groups-dialog/attributes-dialog.tsx new file mode 100644 index 00000000..f6ab8c13 --- /dev/null +++ b/src/views-components/groups-dialog/attributes-dialog.tsx @@ -0,0 +1,101 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import * as React from "react"; +import { Dialog, DialogTitle, DialogContent, DialogActions, Button, Typography, Grid } from "@material-ui/core"; +import { WithDialogProps } from "~/store/dialog/with-dialog"; +import { withDialog } from '~/store/dialog/with-dialog'; +import { WithStyles, withStyles } from '@material-ui/core/styles'; +import { ArvadosTheme } from '~/common/custom-theme'; +import { compose } from "redux"; +import { GroupResource } from "~/models/group"; +import { GROUP_ATTRIBUTES_DIALOG } from "~/store/groups-panel/groups-panel-actions"; + +type CssRules = 'rightContainer' | 'leftContainer' | 'spacing'; + +const styles = withStyles((theme: ArvadosTheme) => ({ + rightContainer: { + textAlign: 'right', + paddingRight: theme.spacing.unit * 2, + color: theme.palette.grey["500"] + }, + leftContainer: { + textAlign: 'left', + paddingLeft: theme.spacing.unit * 2 + }, + spacing: { + paddingTop: theme.spacing.unit * 2 + }, +})); + +interface GroupAttributesDataProps { + data: GroupResource; +} + +type GroupAttributesProps = GroupAttributesDataProps & WithStyles; + +export const GroupAttributesDialog = compose( + withDialog(GROUP_ATTRIBUTES_DIALOG), + styles)( + (props: WithDialogProps & GroupAttributesProps) => + + Attributes + + + {props.data && attributes(props.data, props.classes)} + + + + + + + ); + +const attributes = (group: GroupResource, classes: any) => { + const { uuid, ownerUuid, createdAt, modifiedAt, modifiedByClientUuid, modifiedByUserUuid, name, deleteAt, description, etag, href, isTrashed, trashAt} = group; + return ( + + + + {name && Name} + {ownerUuid && Owner uuid} + {createdAt && Created at} + {modifiedAt && Modified at} + {modifiedByUserUuid && Modified by user uuid} + {modifiedByClientUuid && Modified by client uuid} + {uuid && uuid} + {deleteAt && Delete at} + {description && Description} + {etag && Etag} + {href && Href} + {isTrashed && Is trashed} + {trashAt && Trashed at} + + + {name} + {ownerUuid} + {createdAt} + {modifiedAt} + {modifiedByUserUuid} + {modifiedByClientUuid} + {uuid} + {deleteAt} + {description} + {etag} + {href} + {isTrashed} + {trashAt} + + + + ); +}; diff --git a/src/views-components/groups-dialog/remove-dialog.ts b/src/views-components/groups-dialog/remove-dialog.ts new file mode 100644 index 00000000..8220198e --- /dev/null +++ b/src/views-components/groups-dialog/remove-dialog.ts @@ -0,0 +1,21 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { Dispatch, compose } from 'redux'; +import { connect } from "react-redux"; +import { ConfirmationDialog } from "~/components/confirmation-dialog/confirmation-dialog"; +import { withDialog, WithDialogProps } from "~/store/dialog/with-dialog"; +import { removeGroup, GROUP_REMOVE_DIALOG } from '~/store/groups-panel/groups-panel-actions'; + +const mapDispatchToProps = (dispatch: Dispatch, props: WithDialogProps) => ({ + onConfirm: () => { + props.closeDialog(); + dispatch(removeGroup(props.data.uuid)); + } +}); + +export const RemoveGroupDialog = compose( + withDialog(GROUP_REMOVE_DIALOG), + connect(null, mapDispatchToProps) +)(ConfirmationDialog); \ No newline at end of file diff --git a/src/views/groups-panel/groups-panel.tsx b/src/views/groups-panel/groups-panel.tsx index 39028ecf..f3f3701b 100644 --- a/src/views/groups-panel/groups-panel.tsx +++ b/src/views/groups-panel/groups-panel.tsx @@ -15,6 +15,12 @@ import { ResourceName } from '~/views-components/data-explorer/renderers'; import { createTree } from '~/models/tree'; import { GROUPS_PANEL_ID, openCreateGroupDialog } from '~/store/groups-panel/groups-panel-actions'; import { noop } from 'lodash/fp'; +import { ContextMenuKind } from '~/views-components/context-menu/context-menu'; +import { getResource, ResourcesState } from '~/store/resources/resources'; +import { GroupResource } from '~/models/group'; +import { RootState } from '~/store/store'; +import { Dispatch } from 'redux'; +import { openContextMenu } from '~/store/context-menu/context-menu-actions'; export enum GroupsPanelColumnNames { GROUP = "Name", @@ -47,15 +53,25 @@ export const groupsPanelColumns: DataColumns = [ }, ]; +const mapStateToProps = (state: RootState) => { + return { + resources: state.resources + }; +}; + +const mapDispatchToProps = (dispatch: Dispatch) => ({ + onContextMenu: (event: React.MouseEvent, item: any) => dispatch(openContextMenu(event, item)), + onNewGroup: openCreateGroupDialog +}); + export interface GroupsPanelProps { onNewGroup: () => void; + onContextMenu: (event: React.MouseEvent, item: any) => void; + resources: ResourcesState; } export const GroupsPanel = connect( - null, - { - onNewGroup: openCreateGroupDialog - } + mapStateToProps, mapDispatchToProps )( class GroupsPanel extends React.Component { @@ -65,7 +81,7 @@ export const GroupsPanel = connect( id={GROUPS_PANEL_ID} onRowClick={noop} onRowDoubleClick={noop} - onContextMenu={noop} + onContextMenu={this.handleContextMenu} contextMenuColumn={true} hideColumnSelector actions={ @@ -80,4 +96,17 @@ export const GroupsPanel = connect( } /> ); } + + handleContextMenu = (event: React.MouseEvent, resourceUuid: string) => { + const resource = getResource(resourceUuid)(this.props.resources); + if (resource) { + this.props.onContextMenu(event, { + name: '', + uuid: resource.uuid, + ownerUuid: resource.ownerUuid, + kind: resource.kind, + menuKind: ContextMenuKind.GROUPS + }); + } + } }); diff --git a/src/views/workbench/workbench.tsx b/src/views/workbench/workbench.tsx index 2a773184..62c10f24 100644 --- a/src/views/workbench/workbench.tsx +++ b/src/views/workbench/workbench.tsx @@ -77,6 +77,8 @@ import { CreateUserDialog } from '~/views-components/dialog-forms/create-user-di import { HelpApiClientAuthorizationDialog } from '~/views-components/api-client-authorizations-dialog/help-dialog'; import { GroupsPanel } from '~/views/groups-panel/groups-panel'; import { CreateGroupDialog } from '~/views-components/dialog-forms/create-group-dialog'; +import { RemoveGroupDialog } from '~/views-components/groups-dialog/remove-dialog'; +import { GroupAttributesDialog } from '~/views-components/groups-dialog/attributes-dialog'; type CssRules = 'root' | 'container' | 'splitter' | 'asidePanel' | 'contentWrapper' | 'content'; @@ -181,6 +183,7 @@ export const WorkbenchPanel = + @@ -193,6 +196,7 @@ export const WorkbenchPanel = + -- 2.30.2