From: Stephen Smith Date: Mon, 4 Apr 2022 21:31:00 +0000 (-0400) Subject: Merge branch '18559-user-profile' into main. Closes #18559 X-Git-Tag: 2.4.0~1 X-Git-Url: https://git.arvados.org/arvados-workbench2.git/commitdiff_plain/41f6f1e495c82fcfa79b87cf718fa2e9cd91c726?hp=-c Merge branch '18559-user-profile' into main. Closes #18559 Arvados-DCO-1.1-Signed-off-by: Stephen Smith --- 41f6f1e495c82fcfa79b87cf718fa2e9cd91c726 diff --combined src/components/icon/icon.tsx index 557e22e7,4d17dd28..19b4beea --- a/src/components/icon/icon.tsx +++ b/src/components/icon/icon.tsx @@@ -65,14 -65,15 +65,18 @@@ import VpnKey from '@material-ui/icons/ import LinkOutlined from '@material-ui/icons/LinkOutlined'; import RemoveRedEye from '@material-ui/icons/RemoveRedEye'; import Computer from '@material-ui/icons/Computer'; +import WrapText from '@material-ui/icons/WrapText'; +import TextIncrease from '@material-ui/icons/ZoomIn'; +import TextDecrease from '@material-ui/icons/ZoomOut'; + import CropFreeSharp from '@material-ui/icons/CropFreeSharp'; + import ExitToApp from '@material-ui/icons/ExitToApp'; + import CheckCircleOutline from '@material-ui/icons/CheckCircleOutline'; + import RemoveCircleOutline from '@material-ui/icons/RemoveCircleOutline'; + import NotInterested from '@material-ui/icons/NotInterested'; // Import FontAwesome icons import { library } from '@fortawesome/fontawesome-svg-core'; import { faPencilAlt, faSlash, faUsers, faEllipsisH } from '@fortawesome/free-solid-svg-icons'; - import { CropFreeSharp } from '@material-ui/icons'; library.add( faPencilAlt, faSlash, @@@ -176,6 -177,8 +180,11 @@@ export const CanReadIcon: IconType = (p export const CanWriteIcon: IconType = (props) => ; export const CanManageIcon: IconType = (props) => ; export const AddUserIcon: IconType = (props) => ; +export const WordWrapIcon: IconType = (props) => ; +export const TextIncreaseIcon: IconType = (props) => ; +export const TextDecreaseIcon: IconType = (props) => ; + export const DeactivateUserIcon: IconType = (props) => ; + export const LoginAsIcon: IconType = (props) => ; + export const ActiveIcon: IconType = (props) => ; + export const SetupIcon: IconType = (props) => ; + export const InactiveIcon: IconType = (props) => ; diff --combined src/routes/route-change-handlers.ts index 5e07e6e8,b9158083..5b3ce668 --- a/src/routes/route-change-handlers.ts +++ b/src/routes/route-change-handlers.ts @@@ -27,6 -27,7 +27,6 @@@ const handleLocationChange = (store: Ro const publicFavoritesMatch = Routes.matchPublicFavoritesRoute(pathname); const trashMatch = Routes.matchTrashRoute(pathname); const processMatch = Routes.matchProcessRoute(pathname); - const processLogMatch = Routes.matchProcessLogRoute(pathname); const repositoryMatch = Routes.matchRepositoriesRoute(pathname); const searchResultsMatch = Routes.matchSearchResultsRoute(pathname); const sharedWithMeMatch = Routes.matchSharedWithMeRoute(pathname); @@@ -41,7 -42,8 +41,8 @@@ const apiClientAuthorizationsMatch = Routes.matchApiClientAuthorizationsRoute(pathname); const myAccountMatch = Routes.matchMyAccountRoute(pathname); const linkAccountMatch = Routes.matchLinkAccountRoute(pathname); - const userMatch = Routes.matchUsersRoute(pathname); + const usersMatch = Routes.matchUsersRoute(pathname); + const userProfileMatch = Routes.matchUserProfileRoute(pathname); const groupsMatch = Routes.matchGroupsRoute(pathname); const groupDetailsMatch = Routes.matchGroupDetailsRoute(pathname); const linksMatch = Routes.matchLinksRoute(pathname); @@@ -70,6 -72,8 +71,6 @@@ store.dispatch(WorkbenchActions.loadTrash()); } else if (processMatch) { store.dispatch(WorkbenchActions.loadProcess(processMatch.params.id)); - } else if (processLogMatch) { - store.dispatch(WorkbenchActions.loadProcessLog(processLogMatch.params.id)); } else if (rootMatch) { store.dispatch(navigateToRootProject); } else if (sharedWithMeMatch) { @@@ -97,11 -101,13 +98,13 @@@ } else if (apiClientAuthorizationsMatch) { store.dispatch(WorkbenchActions.loadApiClientAuthorizations); } else if (myAccountMatch) { - store.dispatch(WorkbenchActions.loadMyAccount); + store.dispatch(WorkbenchActions.loadUserProfile()); } else if (linkAccountMatch) { store.dispatch(WorkbenchActions.loadLinkAccount); - } else if (userMatch) { + } else if (usersMatch) { store.dispatch(WorkbenchActions.loadUsers); + } else if (userProfileMatch) { + store.dispatch(WorkbenchActions.loadUserProfile(userProfileMatch.params.id)); } else if (groupsMatch) { store.dispatch(WorkbenchActions.loadGroupsPanel); } else if (groupDetailsMatch) { diff --combined src/routes/routes.ts index d7257b51,205ae08f..50689ec3 --- a/src/routes/routes.ts +++ b/src/routes/routes.ts @@@ -25,6 -25,7 +25,6 @@@ export const Routes = PROCESSES: `/processes/:id(${RESOURCE_UUID_PATTERN})`, FAVORITES: '/favorites', TRASH: '/trash', - PROCESS_LOGS: `/process-logs/:id(${RESOURCE_UUID_PATTERN})`, REPOSITORIES: '/repositories', SHARED_WITH_ME: '/shared-with-me', RUN_PROCESS: '/run-process', @@@ -39,6 -40,7 +39,7 @@@ LINK_ACCOUNT: '/link_account', KEEP_SERVICES: `/keep-services`, USERS: '/users', + USER_PROFILE: `/user/:id(${RESOURCE_UUID_PATTERN})`, API_CLIENT_AUTHORIZATIONS: `/api_client_authorizations`, GROUPS: '/groups', GROUP_DETAILS: `/group/:id(${RESOURCE_UUID_PATTERN})`, @@@ -94,8 -96,12 +95,10 @@@ export const getNavUrl = (uuid: string export const getProcessUrl = (uuid: string) => `/processes/${uuid}`; -export const getProcessLogUrl = (uuid: string) => `/process-logs/${uuid}`; - export const getGroupUrl = (uuid: string) => `/group/${uuid}`; + export const getUserProfileUrl = (uuid: string) => `/user/${uuid}`; + export interface ResourceRouteParams { id: string; } @@@ -121,6 -127,9 +124,6 @@@ export const matchCollectionRoute = (ro export const matchProcessRoute = (route: string) => matchPath(route, { path: Routes.PROCESSES }); -export const matchProcessLogRoute = (route: string) => - matchPath(route, { path: Routes.PROCESS_LOGS }); - export const matchSharedWithMeRoute = (route: string) => matchPath(route, { path: Routes.SHARED_WITH_ME }); @@@ -169,6 -178,9 +172,9 @@@ export const matchFedTokenRoute = (rout export const matchUsersRoute = (route: string) => matchPath(route, { path: Routes.USERS }); + export const matchUserProfileRoute = (route: string) => + matchPath(route, { path: Routes.USER_PROFILE }); + export const matchApiClientAuthorizationsRoute = (route: string) => matchPath(route, { path: Routes.API_CLIENT_AUTHORIZATIONS }); diff --combined src/store/context-menu/context-menu-actions.ts index 336817ea,fb5da9fc..1116949a --- a/src/store/context-menu/context-menu-actions.ts +++ b/src/store/context-menu/context-menu-actions.ts @@@ -190,7 -190,7 +190,7 @@@ export const openProcessContextMenu = ( description: res.description, outputUuid: res.outputUuid || '', workflowUuid: res.properties.workflowUuid || '', - menuKind: ContextMenuKind.PROCESS + menuKind: ContextMenuKind.PROCESS_RESOURCE })); } }; @@@ -208,6 -208,17 +208,17 @@@ export const openPermissionEditContextM } }; + 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!; diff --combined src/store/navigation/navigation-action.ts index 49f56591,776409c0..1cdb6784 --- a/src/store/navigation/navigation-action.ts +++ b/src/store/navigation/navigation-action.ts @@@ -6,11 -6,12 +6,12 @@@ import { Dispatch, compose, AnyAction import { push } from "react-router-redux"; import { ResourceKind, extractUuidKind } from 'models/resource'; import { SidePanelTreeCategory } from '../side-panel-tree/side-panel-tree-actions'; - import { Routes, getGroupUrl, getNavUrl } from 'routes/routes'; -import { Routes, getProcessLogUrl, getGroupUrl, getNavUrl, getUserProfileUrl } from 'routes/routes'; ++import { Routes, getGroupUrl, getNavUrl, getUserProfileUrl } from 'routes/routes'; import { RootState } from 'store/store'; import { ServiceRepository } from 'services/services'; import { pluginConfig } from 'plugins'; import { snackbarActions, SnackbarKind } from 'store/snackbar/snackbar-actions'; + import { USERS_PANEL_LABEL, MY_ACCOUNT_PANEL_LABEL } from 'store/breadcrumbs/breadcrumbs-actions'; const navigationNotAvailable = (id: string) => snackbarActions.OPEN_SNACKBAR({ @@@ -69,6 -70,12 +70,12 @@@ export const navigateTo = (uuid: string case SidePanelTreeCategory.ALL_PROCESSES: dispatch(navigateToAllProcesses); return; + case USERS_PANEL_LABEL: + dispatch(navigateToUsers); + return; + case MY_ACCOUNT_PANEL_LABEL: + dispatch(navigateToMyAccount); + return; } dispatch(navigationNotAvailable(uuid)); @@@ -99,6 -106,8 +106,6 @@@ export const pushOrGoto = (url: string) }; -export const navigateToProcessLogs = compose(push, getProcessLogUrl); - export const navigateToRootProject = (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { navigateTo(SidePanelTreeCategory.PROJECTS)(dispatch, getState); }; @@@ -135,6 -144,8 +142,8 @@@ export const navigateToKeepServices = p export const navigateToUsers = push(Routes.USERS); + export const navigateToUserProfile = compose(push, getUserProfileUrl); + export const navigateToApiClientAuthorizations = push(Routes.API_CLIENT_AUTHORIZATIONS); export const navigateToGroups = push(Routes.GROUPS); diff --combined src/store/workbench/workbench-actions.ts index 7958463a,5e83ed7b..ba405cb8 --- a/src/store/workbench/workbench-actions.ts +++ b/src/store/workbench/workbench-actions.ts @@@ -31,7 -31,10 +31,10 @@@ import setProcessBreadcrumbs, setSharedWithMeBreadcrumbs, setSidePanelBreadcrumbs, - setTrashBreadcrumbs + setTrashBreadcrumbs, + setUsersBreadcrumbs, + setMyAccountBreadcrumbs, + setUserProfileBreadcrumbs, } from 'store/breadcrumbs/breadcrumbs-actions'; import { navigateTo, navigateToRootProject } from 'store/navigation/navigation-action'; import { MoveToFormDialogData } from 'store/move-to-dialog/move-to-dialog'; @@@ -49,6 -52,7 +52,6 @@@ import * as processUpdateActions from ' import * as processCopyActions from 'store/processes/process-copy-actions'; import { trashPanelColumns } from "views/trash-panel/trash-panel"; import { loadTrashPanel, trashPanelActions } from "store/trash-panel/trash-panel-action"; -import { initProcessLogsPanel } from 'store/process-logs-panel/process-logs-panel-actions'; import { loadProcessPanel } from 'store/process-panel/process-panel-actions'; import { loadSharedWithMePanel, @@@ -57,7 -61,6 +60,6 @@@ import { CopyFormDialogData } from 'store/copy-dialog/copy-dialog'; import { loadWorkflowPanel, workflowPanelActions } from 'store/workflow-panel/workflow-panel-actions'; import { loadSshKeysPanel } from 'store/auth/auth-action-ssh'; - import { loadMyAccountPanel } from 'store/my-account/my-account-panel-actions'; import { loadLinkAccountPanel, linkAccountPanelActions } from 'store/link-account-panel/link-account-panel-actions'; import { loadSiteManagerPanel } from 'store/auth/auth-action-session'; import { workflowPanelColumns } from 'views/workflow-panel/workflow-panel-view'; @@@ -79,6 -82,7 +81,7 @@@ import { loadVirtualMachinesPanel } fro import { loadRepositoriesPanel } from 'store/repositories/repositories-actions'; import { loadKeepServicesPanel } from 'store/keep-services/keep-services-actions'; import { loadUsersPanel, userBindedActions } from 'store/users/users-actions'; + import * as userProfilePanelActions from 'store/user-profile/user-profile-actions'; import { linkPanelActions, loadLinkPanel } from 'store/link-panel/link-panel-actions'; import { linkPanelColumns } from 'views/link-panel/link-panel-root'; import { userPanelColumns } from 'views/user-panel/user-panel'; @@@ -100,6 -104,7 +103,7 @@@ import { allProcessesPanelColumns } fro import { collectionPanelFilesAction } from '../collection-panel/collection-panel-files/collection-panel-files-actions'; import { createTree } from 'models/tree'; import { AdminMenuIcon } from 'components/icon/icon'; + import { userProfileGroupsColumns } from 'views/user-profile-panel/user-profile-panel-root'; export const WORKBENCH_LOADING_SCREEN = 'workbenchLoadingScreen'; @@@ -138,6 -143,7 +142,7 @@@ export const loadWorkbench = () = dispatch(groupPanelActions.GroupsPanelActions.SET_COLUMNS({ columns: groupsPanelColumns })); dispatch(groupDetailsPanelActions.GroupMembersPanelActions.SET_COLUMNS({ columns: groupDetailsMembersPanelColumns })); dispatch(groupDetailsPanelActions.GroupPermissionsPanelActions.SET_COLUMNS({ columns: groupDetailsPermissionsPanelColumns })); + dispatch(userProfilePanelActions.UserProfileGroupsActions.SET_COLUMNS({ columns: userProfileGroupsColumns })); dispatch(linkPanelActions.SET_COLUMNS({ columns: linkPanelColumns })); dispatch(apiClientAuthorizationsActions.SET_COLUMNS({ columns: apiClientAuthorizationPanelColumns })); dispatch(collectionsContentAddressActions.SET_COLUMNS({ columns: collectionContentAddressPanelColumns })); @@@ -416,6 -422,15 +421,6 @@@ export const copyProcess = (data: CopyF } }; -export const loadProcessLog = (uuid: string) => - handleFirstTimeLoad( - async (dispatch: Dispatch) => { - const process = await dispatch(processesActions.loadProcess(uuid)); - dispatch(setProcessBreadcrumbs(uuid)); - dispatch(initProcessLogsPanel(uuid)); - await dispatch(activateSidePanelTreeItem(process.containerRequest.ownerUuid)); - }); - export const resourceIsNotLoaded = (uuid: string) => snackbarActions.OPEN_SNACKBAR({ message: `Resource identified by ${uuid} is not loaded.`, @@@ -504,10 -519,18 +509,18 @@@ export const loadSiteManager = handleFi await dispatch(loadSiteManagerPanel()); }); - export const loadMyAccount = handleFirstTimeLoad( - (dispatch: Dispatch) => { - dispatch(loadMyAccountPanel()); - }); + export const loadUserProfile = (userUuid?: string) => + handleFirstTimeLoad( + (dispatch: Dispatch) => { + if (userUuid) { + dispatch(setUserProfileBreadcrumbs(userUuid)); + dispatch(userProfilePanelActions.loadUserProfilePanel(userUuid)); + } else { + dispatch(setMyAccountBreadcrumbs()); + dispatch(userProfilePanelActions.loadUserProfilePanel()); + } + } + ); export const loadLinkAccount = handleFirstTimeLoad( (dispatch: Dispatch) => { @@@ -522,7 -545,7 +535,7 @@@ export const loadKeepServices = handleF export const loadUsers = handleFirstTimeLoad( async (dispatch: Dispatch) => { await dispatch(loadUsersPanel()); - dispatch(setBreadcrumbs([{ label: 'Users' }])); + dispatch(setUsersBreadcrumbs()); }); export const loadApiClientAuthorizations = handleFirstTimeLoad( diff --combined src/views/workbench/workbench.tsx index fe97bd3b,0d1a8950..28fae4cd --- a/src/views/workbench/workbench.tsx +++ b/src/views/workbench/workbench.tsx @@@ -20,6 -20,7 +20,6 @@@ import { MultipleFilesRemoveDialog } fr import { Routes } from 'routes/routes'; import { SidePanel } from 'views-components/side-panel/side-panel'; import { ProcessPanel } from 'views/process-panel/process-panel'; -import { ProcessLogPanel } from 'views/process-log-panel/process-log-panel'; import { ChangeWorkflowDialog } from 'views-components/run-process-dialog/change-workflow-dialog'; import { CreateProjectDialog } from 'views-components/dialog-forms/create-project-dialog'; import { CreateCollectionDialog } from 'views-components/dialog-forms/create-collection-dialog'; @@@ -46,7 -47,7 +46,7 @@@ import { SearchResultsPanel } from 'vie import { SshKeyPanel } from 'views/ssh-key-panel/ssh-key-panel'; import { SshKeyAdminPanel } from 'views/ssh-key-panel/ssh-key-admin-panel'; import { SiteManagerPanel } from "views/site-manager-panel/site-manager-panel"; - import { MyAccountPanel } from 'views/my-account-panel/my-account-panel'; + import { UserProfilePanel } from 'views/user-profile-panel/user-profile-panel'; import { SharingDialog } from 'views-components/sharing-dialog/sharing-dialog'; import { NotFoundDialog } from 'views-components/not-found-dialog/not-found-dialog'; import { AdvancedTabDialog } from 'views-components/advanced-tab-dialog/advanced-tab-dialog'; @@@ -79,8 -80,9 +79,9 @@@ import { UserPanel } from 'views/user-p import { UserAttributesDialog } from 'views-components/user-dialog/attributes-dialog'; import { CreateUserDialog } from 'views-components/dialog-forms/create-user-dialog'; import { HelpApiClientAuthorizationDialog } from 'views-components/api-client-authorizations-dialog/help-dialog'; - import { UserManageDialog } from 'views-components/user-dialog/manage-dialog'; - import { SetupShellAccountDialog } from 'views-components/dialog-forms/setup-shell-account-dialog'; + import { DeactivateDialog } from 'views-components/user-dialog/deactivate-dialog'; + import { ActivateDialog } from 'views-components/user-dialog/activate-dialog'; + import { SetupDialog } from 'views-components/user-dialog/setup-dialog'; import { GroupsPanel } from 'views/groups-panel/groups-panel'; import { RemoveGroupDialog } from 'views-components/groups-dialog/remove-dialog'; import { GroupAttributesDialog } from 'views-components/groups-dialog/attributes-dialog'; @@@ -157,6 -159,7 +158,6 @@@ let routes = < - @@@ -170,7 -173,8 +171,8 @@@ - + + @@@ -257,7 -261,6 +259,6 @@@ export const WorkbenchPanel - @@@ -265,7 -268,9 +266,9 @@@ - + + +