From 32f8f97903802e58b48ed307669c236750d8ee3e Mon Sep 17 00:00:00 2001 From: Pawel Kowalczyk Date: Wed, 5 Dec 2018 17:05:29 +0100 Subject: [PATCH] creating-user Feature #14504 Arvados-DCO-1.1-Signed-off-by: Pawel Kowalczyk --- .../data-explorer/data-explorer.tsx | 16 ++- src/store/users/users-actions.ts | 41 +++---- src/validators/validators.tsx | 3 + .../action-sets/user-action-set.ts | 4 +- .../dialog-create/dialog-user-create.tsx | 28 +++++ .../dialog-forms/create-user-dialog.ts | 19 ++++ .../form-fields/collection-form-fields.tsx | 6 +- .../form-fields/process-form-fields.tsx | 2 +- .../form-fields/user-form-fields.tsx | 54 +++++++++ .../user-dialog/attributes-dialog.tsx | 22 ++-- src/views/user-panel/user-panel.tsx | 105 ++++++++++++------ src/views/workbench/workbench.tsx | 2 + 12 files changed, 219 insertions(+), 83 deletions(-) create mode 100644 src/views-components/dialog-create/dialog-user-create.tsx create mode 100644 src/views-components/dialog-forms/create-user-dialog.ts create mode 100644 src/views-components/form-fields/user-form-fields.tsx diff --git a/src/components/data-explorer/data-explorer.tsx b/src/components/data-explorer/data-explorer.tsx index 69f93dda..d906a32c 100644 --- a/src/components/data-explorer/data-explorer.tsx +++ b/src/components/data-explorer/data-explorer.tsx @@ -13,7 +13,7 @@ import { createTree } from '~/models/tree'; import { DataTableFilters } from '~/components/data-table-filters/data-table-filters-tree'; import { MoreOptionsIcon } from '~/components/icon/icon'; -type CssRules = 'searchBox' | "toolbar" | "footer" | "root" | 'moreOptionsButton'; +type CssRules = 'searchBox' | "toolbar" | "footer" | "root" | 'moreOptionsButton' | 'rootUserPanel'; const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ searchBox: { @@ -28,6 +28,10 @@ const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ root: { height: '100%' }, + rootUserPanel: { + height: '100%', + boxShadow: 'none' + }, moreOptionsButton: { padding: 0 } @@ -44,7 +48,7 @@ interface DataExplorerDataProps { contextMenuColumn: boolean; dataTableDefaultView?: React.ReactNode; working?: boolean; - isColumnSelectorHidden?: boolean; + isUserPanel?: boolean; } interface DataExplorerActionProps { @@ -75,17 +79,17 @@ export const DataExplorer = withStyles(styles)( columns, onContextMenu, onFiltersChange, onSortToggle, working, extractKey, rowsPerPage, rowsPerPageOptions, onColumnToggle, searchValue, onSearch, items, itemsAvailable, onRowClick, onRowDoubleClick, classes, - dataTableDefaultView, isColumnSelectorHidden + dataTableDefaultView, isUserPanel } = this.props; - return - + return +
- {!isColumnSelectorHidden && }
diff --git a/src/store/users/users-actions.ts b/src/store/users/users-actions.ts index 43d9e6f3..ca6edd62 100644 --- a/src/store/users/users-actions.ts +++ b/src/store/users/users-actions.ts @@ -24,7 +24,15 @@ export type UsersActions = UnionOf; export const USERS_PANEL_ID = 'usersPanel'; export const USER_ATTRIBUTES_DIALOG = 'userAttributesDialog'; export const USER_CREATE_FORM_NAME = 'repositoryCreateFormName'; -export const USER_REMOVE_DIALOG = 'repositoryRemoveDialog'; + +export interface UserCreateFormDialogData { + firstName: string; + lastName: string; + email: string; + identityUrl: string; + virtualMachineName: string; + groupVirtualMachine: string; +} export const openUserAttributes = (uuid: string) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { @@ -41,19 +49,17 @@ export const openUserCreateDialog = () => dispatch(dialogActions.OPEN_DIALOG({ id: USER_CREATE_FORM_NAME, data: { user } })); }; -export const createUser = (user: UserResource) => +export const createUser = (user: UserCreateFormDialogData) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - const userUuid = await services.authService.getUuid(); - const user = await services.userService.get(userUuid!); dispatch(startSubmit(USER_CREATE_FORM_NAME)); try { - // const newUser = await services.repositoriesService.create({ name: `${user.username}/${repository.name}` }); + const newUser = await services.userService.create({ ...user }); dispatch(dialogActions.CLOSE_DIALOG({ id: USER_CREATE_FORM_NAME })); dispatch(reset(USER_CREATE_FORM_NAME)); dispatch(snackbarActions.OPEN_SNACKBAR({ message: "User has been successfully created.", hideDuration: 2000, kind: SnackbarKind.SUCCESS })); dispatch(loadUsersData()); - // return newUser; - return; + dispatch(userBindedActions.REQUEST_ITEMS()); + return newUser; } catch (e) { const error = getCommonResourceServiceError(e); if (error === CommonResourceServiceError.NAME_HAS_ALREADY_BEEN_TAKEN) { @@ -63,27 +69,6 @@ export const createUser = (user: UserResource) => } }; -export const openRemoveUsersDialog = (uuid: string) => - (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - dispatch(dialogActions.OPEN_DIALOG({ - id: USER_REMOVE_DIALOG, - data: { - title: 'Remove user', - text: 'Are you sure you want to remove this user?', - confirmButtonLabel: 'Remove', - uuid - } - })); - }; - -export const removeUser = (uuid: string) => - async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removing ...' })); - await services.userService.delete(uuid); - dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removed.', hideDuration: 2000, kind: SnackbarKind.SUCCESS })); - dispatch(loadUsersData()); - }; - export const userBindedActions = bindDataExplorerActions(USERS_PANEL_ID); export const openUsersPanel = () => diff --git a/src/validators/validators.tsx b/src/validators/validators.tsx index c601df17..464f1900 100644 --- a/src/validators/validators.tsx +++ b/src/validators/validators.tsx @@ -24,5 +24,8 @@ export const PROCESS_NAME_VALIDATION = [require, maxLength(255)]; export const REPOSITORY_NAME_VALIDATION = [require, maxLength(255)]; +export const USER_EMAIL_VALIDATION = [require, maxLength(255)]; +export const USER_LENGTH_VALIDATION = [maxLength(255)]; + export const SSH_KEY_PUBLIC_VALIDATION = [require, isRsaKey, maxLength(1024)]; export const SSH_KEY_NAME_VALIDATION = [require, maxLength(255)]; diff --git a/src/views-components/context-menu/action-sets/user-action-set.ts b/src/views-components/context-menu/action-sets/user-action-set.ts index e06a7c21..68098cff 100644 --- a/src/views-components/context-menu/action-sets/user-action-set.ts +++ b/src/views-components/context-menu/action-sets/user-action-set.ts @@ -3,7 +3,7 @@ // SPDX-License-Identifier: AGPL-3.0 import { ContextMenuActionSet } from "~/views-components/context-menu/context-menu-action-set"; -import { AdvancedIcon, ProjectIcon, AttributesIcon, ShareIcon } from "~/components/icon/icon"; +import { AdvancedIcon, ProjectIcon, AttributesIcon, UserPanelIcon } from "~/components/icon/icon"; import { openAdvancedTabDialog } from '~/store/advanced-tab/advanced-tab'; import { openUserAttributes } from "~/store/users/users-actions"; @@ -28,7 +28,7 @@ export const userActionSet: ContextMenuActionSet = [[{ }, { name: "Manage", - icon: ShareIcon, + icon: UserPanelIcon, execute: (dispatch, { uuid }) => { dispatch(openAdvancedTabDialog(uuid)); } diff --git a/src/views-components/dialog-create/dialog-user-create.tsx b/src/views-components/dialog-create/dialog-user-create.tsx new file mode 100644 index 00000000..bf135e8c --- /dev/null +++ b/src/views-components/dialog-create/dialog-user-create.tsx @@ -0,0 +1,28 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import * as React from 'react'; +import { InjectedFormProps } from 'redux-form'; +import { WithDialogProps } from '~/store/dialog/with-dialog'; +import { FormDialog } from '~/components/form-dialog/form-dialog'; +import { UserFirstNameField, UserLastNameField, UserEmailField, UserIdentityUrlField, UserVirtualMachineField, UserGroupsVirtualMachineField } from '~/views-components/form-fields/user-form-fields'; + +type DialogUserProps = WithDialogProps<{}> & InjectedFormProps; + +export const UserRepositoryCreate = (props: DialogUserProps) => + ; + +const UserAddFields = () => + + + + + + +; diff --git a/src/views-components/dialog-forms/create-user-dialog.ts b/src/views-components/dialog-forms/create-user-dialog.ts new file mode 100644 index 00000000..cb46204e --- /dev/null +++ b/src/views-components/dialog-forms/create-user-dialog.ts @@ -0,0 +1,19 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { compose } from "redux"; +import { reduxForm } from 'redux-form'; +import { withDialog } from "~/store/dialog/with-dialog"; +import { USER_CREATE_FORM_NAME, createUser, UserCreateFormDialogData } from "~/store/users/users-actions"; +import { UserRepositoryCreate } from "~/views-components/dialog-create/dialog-user-create"; + +export const CreateUserDialog = compose( + withDialog(USER_CREATE_FORM_NAME), + reduxForm({ + form: USER_CREATE_FORM_NAME, + onSubmit: (data, dispatch) => { + dispatch(createUser(data)); + } + }) +)(UserRepositoryCreate); \ No newline at end of file diff --git a/src/views-components/form-fields/collection-form-fields.tsx b/src/views-components/form-fields/collection-form-fields.tsx index 2d2a7c80..c02996d8 100644 --- a/src/views-components/form-fields/collection-form-fields.tsx +++ b/src/views-components/form-fields/collection-form-fields.tsx @@ -3,11 +3,11 @@ // SPDX-License-Identifier: AGPL-3.0 import * as React from "react"; -import { Field, WrappedFieldProps } from "redux-form"; +import { Field } from "redux-form"; import { TextField } from "~/components/text-field/text-field"; import { COLLECTION_NAME_VALIDATION, COLLECTION_DESCRIPTION_VALIDATION, COLLECTION_PROJECT_VALIDATION } from "~/validators/validators"; -import { ProjectTreePicker, ProjectTreePickerField } from "~/views-components/project-tree-picker/project-tree-picker"; -import { PickerIdProp } from '../../store/tree-picker/picker-id'; +import { ProjectTreePickerField } from "~/views-components/project-tree-picker/project-tree-picker"; +import { PickerIdProp } from '~/store/tree-picker/picker-id'; export const CollectionNameField = () => + ; + +export const UserLastNameField = () => + ; + +export const UserEmailField = () => + ; + +export const UserIdentityUrlField = () => + ; + +export const UserVirtualMachineField = () => + ; + +export const UserGroupsVirtualMachineField = () => + ; \ No newline at end of file diff --git a/src/views-components/user-dialog/attributes-dialog.tsx b/src/views-components/user-dialog/attributes-dialog.tsx index 5f711de7..66a48815 100644 --- a/src/views-components/user-dialog/attributes-dialog.tsx +++ b/src/views-components/user-dialog/attributes-dialog.tsx @@ -66,17 +66,17 @@ const attributes = (user: UserResource, classes: any) => { - First name - Last name - Owner uuid - Created at - Modified at - Modified by user uuid - Modified by client uuid - uuid - Href - Identity url - Username + {firstName && First name} + {lastName && Last name} + {ownerUuid && Owner uuid} + {createdAt && Created at} + {modifiedAt && Modified at} + {modifiedByUserUuid && Modified by user uuid} + {modifiedByClientUuid && Modified by client uuid} + {uuid && uuid} + {href && Href} + {identityUrl && Identity url} + {username && Username} {firstName} diff --git a/src/views/user-panel/user-panel.tsx b/src/views/user-panel/user-panel.tsx index ceaba8cb..db44d508 100644 --- a/src/views/user-panel/user-panel.tsx +++ b/src/views/user-panel/user-panel.tsx @@ -3,7 +3,7 @@ // SPDX-License-Identifier: AGPL-3.0 import * as React from 'react'; -import { WithStyles, withStyles, Typography } from '@material-ui/core'; +import { WithStyles, withStyles, Typography, Tabs, Tab, Paper, Button } from '@material-ui/core'; import { DataExplorer } from "~/views-components/data-explorer/data-explorer"; import { connect, DispatchProp } from 'react-redux'; import { DataColumns } from '~/components/data-table/data-table'; @@ -21,24 +21,22 @@ import { ResourceUsername } from "~/views-components/data-explorer/renderers"; import { navigateTo } from "~/store/navigation/navigation-action"; -import { loadDetailsPanel } from "~/store/details-panel/details-panel-action"; import { ContextMenuKind } from "~/views-components/context-menu/context-menu"; import { DataTableDefaultView } from '~/components/data-table-default-view/data-table-default-view'; import { createTree } from '~/models/tree'; -import { compose } from 'redux'; +import { compose, Dispatch } from 'redux'; import { UserResource } from '~/models/user'; -import { ShareMeIcon } from '~/components/icon/icon'; -import { USERS_PANEL_ID } from '~/store/users/users-actions'; +import { ShareMeIcon, AddIcon } from '~/components/icon/icon'; +import { USERS_PANEL_ID, openUserCreateDialog } from '~/store/users/users-actions'; -type UserPanelRules = "toolbar" | "button"; +type UserPanelRules = "button"; const styles = withStyles(theme => ({ - toolbar: { - paddingBottom: theme.spacing.unit * 3, - textAlign: "right" - }, button: { - marginLeft: theme.spacing.unit + marginTop: theme.spacing.unit, + marginRight: theme.spacing.unit * 2, + textAlign: 'right', + alignSelf: 'center' }, })); @@ -124,48 +122,91 @@ interface UserPanelDataProps { resources: ResourcesState; } -type UserPanelProps = UserPanelDataProps & DispatchProp & WithStyles; +interface UserPanelActionProps { + openUserCreateDialog: () => void; + handleRowDoubleClick: (uuid: string) => void; + onContextMenu: (event: React.MouseEvent, item: any) => void; +} + +const mapStateToProps = (state: RootState) => { + return { + resources: state.resources + }; +}; + +const mapDispatchToProps = (dispatch: Dispatch) => ({ + openUserCreateDialog: () => dispatch(openUserCreateDialog()), + handleRowDoubleClick: (uuid: string) => dispatch(navigateTo(uuid)), + onContextMenu: (event: React.MouseEvent, item: any) => dispatch(openContextMenu(event, item)) +}); + +type UserPanelProps = UserPanelDataProps & UserPanelActionProps & DispatchProp & WithStyles; export const UserPanel = compose( styles, - connect((state: RootState) => ({ - resources: state.resources - })))( + connect(mapStateToProps, mapDispatchToProps))( class extends React.Component { + state = { + value: 0, + }; + + componentDidMount() { + this.setState({ value: 0 }); + } + render() { - return - } />; + const { value } = this.state; + return + + + + + {value === 0 && + +
+ +
+ + } /> +
} +
; + } + + handleChange = (event: React.MouseEvent, value: number) => { + this.setState({ value }); } handleContextMenu = (event: React.MouseEvent, resourceUuid: string) => { const resource = getResource(resourceUuid)(this.props.resources); if (resource) { - this.props.dispatch(openContextMenu(event, { + this.props.onContextMenu(event, { name: '', uuid: resource.uuid, ownerUuid: resource.ownerUuid, kind: resource.kind, menuKind: ContextMenuKind.USER - })); + }); } } handleRowDoubleClick = (uuid: string) => { - this.props.dispatch(navigateTo(uuid)); + this.props.handleRowDoubleClick(uuid); } - handleRowClick = (uuid: string) => { - this.props.dispatch(loadDetailsPanel(uuid)); + handleRowClick = () => { + return; } } ); diff --git a/src/views/workbench/workbench.tsx b/src/views/workbench/workbench.tsx index 11285ba9..5a6ba97d 100644 --- a/src/views/workbench/workbench.tsx +++ b/src/views/workbench/workbench.tsx @@ -66,6 +66,7 @@ import { VirtualMachineAttributesDialog } from '~/views-components/virtual-machi import { RemoveVirtualMachineDialog } from '~/views-components/virtual-machines-dialog/remove-dialog'; import { UserPanel } from '~/views/user-panel/user-panel'; import { UserAttributesDialog } from '~/views-components/user-dialog/attributes-dialog'; +import { CreateUserDialog } from '~/views-components/dialog-forms/create-user-dialog'; type CssRules = 'root' | 'container' | 'splitter' | 'asidePanel' | 'contentWrapper' | 'content'; @@ -159,6 +160,7 @@ export const WorkbenchPanel = + -- 2.30.2