X-Git-Url: https://git.arvados.org/arvados-workbench2.git/blobdiff_plain/a3658215b1129e78e2dfd7496e1d5de8263b2351..add698bee2d2b7002a99aa08ac99b93179e2a535:/src/views-components/data-explorer/renderers.tsx?ds=inline diff --git a/src/views-components/data-explorer/renderers.tsx b/src/views-components/data-explorer/renderers.tsx index 457de504..9c24a677 100644 --- a/src/views-components/data-explorer/renderers.tsx +++ b/src/views-components/data-explorer/renderers.tsx @@ -3,10 +3,33 @@ // SPDX-License-Identifier: AGPL-3.0 import React from 'react'; -import { Grid, Typography, withStyles, Tooltip, IconButton, Checkbox } from '@material-ui/core'; +import { + Grid, + Typography, + withStyles, + Tooltip, + IconButton, + Checkbox, + Chip +} from '@material-ui/core'; import { FavoriteStar, PublicFavoriteStar } from '../favorite-star/favorite-star'; import { Resource, ResourceKind, TrashableResource } from 'models/resource'; -import { ProjectIcon, FilterGroupIcon, CollectionIcon, ProcessIcon, DefaultIcon, ShareIcon, CollectionOldVersionIcon, WorkflowIcon, RemoveIcon, RenameIcon } from 'components/icon/icon'; +import { + FreezeIcon, + ProjectIcon, + FilterGroupIcon, + CollectionIcon, + ProcessIcon, + DefaultIcon, + ShareIcon, + CollectionOldVersionIcon, + WorkflowIcon, + RemoveIcon, + RenameIcon, + ActiveIcon, + SetupIcon, + InactiveIcon, +} from 'components/icon/icon'; import { formatDate, formatFileSize, formatTime } from 'common/formatters'; import { resourceLabel } from 'common/labels'; import { connect, DispatchProp } from 'react-redux'; @@ -21,20 +44,24 @@ import { ResourceStatus as WorkflowStatus } from 'views/workflow-panel/workflow- import { getUuidPrefix, openRunProcess } from 'store/workflow-panel/workflow-panel-actions'; import { openSharingDialog } from 'store/sharing-dialog/sharing-dialog-actions'; import { getUserFullname, getUserDisplayName, User, UserResource } from 'models/user'; -import { toggleIsActive, toggleIsAdmin } from 'store/users/users-actions'; +import { toggleIsAdmin } from 'store/users/users-actions'; import { LinkClass, LinkResource } from 'models/link'; -import { navigateTo, navigateToGroupDetails } from 'store/navigation/navigation-action'; +import { navigateTo, navigateToGroupDetails, navigateToUserProfile } from 'store/navigation/navigation-action'; import { withResourceData } from 'views-components/data-explorer/with-resources'; import { CollectionResource } from 'models/collection'; import { IllegalNamingWarning } from 'components/warning/warning'; import { loadResource } from 'store/resources/resources-actions'; -import { GroupClass, GroupResource } from 'models/group'; +import { BuiltinGroups, getBuiltinGroupUuid, GroupClass, GroupResource, isBuiltinGroup } from 'models/group'; import { openRemoveGroupMemberDialog } from 'store/group-details-panel/group-details-panel-actions'; import { setMemberIsHidden } from 'store/group-details-panel/group-details-panel-actions'; import { formatPermissionLevel } from 'views-components/sharing-dialog/permission-select'; import { PermissionLevel } from 'models/permission'; import { openPermissionEditContextMenu } from 'store/context-menu/context-menu-actions'; import { getUserUuid } from 'common/getuser'; +import { VirtualMachinesResource } from 'models/virtual-machines'; +import { CopyToClipboardSnackbar } from 'components/copy-to-clipboard-snackbar/copy-to-clipboard-snackbar'; +import { ProjectResource } from 'models/project'; +import {ContainerRequestResource} from 'models/container-request' const renderName = (dispatch: Dispatch, item: GroupContentsResource) => { @@ -55,11 +82,32 @@ const renderName = (dispatch: Dispatch, item: GroupContentsResource) => { + { + item.kind === ResourceKind.PROJECT && + } ; }; +const FrozenProject = (props: {item: ProjectResource}) => { + const [fullUsername, setFullusername] = React.useState(null); + const getFullName = React.useCallback(() => { + if (props.item.frozenByUuid) { + setFullusername(); + } + }, [props.item, setFullusername]) + + if (props.item.frozenByUuid) { + + return Project was frozen by {fullUsername}}> + + ; + } else { + return null; + } +} + export const ResourceName = connect( (state: RootState, props: { uuid: string }) => { const resource = getResource(props.uuid)(state.resources); @@ -160,24 +208,32 @@ export const ResourceLastName = connect( return resource || { lastName: '' }; })(renderLastName); -const renderFullName = (item: { firstName: string, lastName: string }) => - {(item.firstName + " " + item.lastName).trim()}; +const renderFullName = (dispatch: Dispatch, item: { uuid: string, firstName: string, lastName: string }, link?: boolean) => { + const displayName = (item.firstName + " " + item.lastName).trim() || item.uuid; + return link ? dispatch(navigateToUserProfile(item.uuid))}> + {displayName} + : + {displayName}; +} -export const ResourceFullName = connect( - (state: RootState, props: { uuid: string }) => { +export const UserResourceFullName = connect( + (state: RootState, props: { uuid: string, link?: boolean }) => { const resource = getResource(props.uuid)(state.resources); - return resource || { firstName: '', lastName: '' }; - })(renderFullName); - + return { item: resource || { uuid: '', firstName: '', lastName: '' }, link: props.link }; + })((props: { item: { uuid: string, firstName: string, lastName: string }, link?: boolean } & DispatchProp) => renderFullName(props.dispatch, props.item, props.link)); const renderUuid = (item: { uuid: string }) => - {item.uuid}; + + {item.uuid} + + ; -export const ResourceUuid = connect( - (state: RootState, props: { uuid: string }) => { - const resource = getResource(props.uuid)(state.resources); - return resource || { uuid: '' }; - })(renderUuid); +export const ResourceUuid = connect((state: RootState, props: { uuid: string }) => ( + getResource(props.uuid)(state.resources) || { uuid: '' } +))(renderUuid); const renderEmail = (item: { email: string }) => {item.email}; @@ -188,45 +244,85 @@ export const ResourceEmail = connect( return resource || { email: '' }; })(renderEmail); -const renderIsActive = (props: { uuid: string, kind: ResourceKind, isActive: boolean, toggleIsActive: (uuid: string) => void }) => { - if (props.kind === ResourceKind.USER) { - return props.toggleIsActive(props.uuid)} />; +enum UserAccountStatus { + ACTIVE = 'Active', + INACTIVE = 'Inactive', + SETUP = 'Setup', + UNKNOWN = '' +} + +const renderAccountStatus = (props: { status: UserAccountStatus }) => + + + {(() => { + switch (props.status) { + case UserAccountStatus.ACTIVE: + return ; + case UserAccountStatus.SETUP: + return ; + case UserAccountStatus.INACTIVE: + return ; + default: + return <>; + } + })()} + + + + {props.status} + + + ; + +const getUserAccountStatus = (state: RootState, props: { uuid: string }) => { + const user = getResource(props.uuid)(state.resources); + // Get membership links for all users group + const allUsersGroupUuid = getBuiltinGroupUuid(state.auth.localCluster, BuiltinGroups.ALL); + const permissions = filterResources((resource: LinkResource) => + resource.kind === ResourceKind.LINK && + resource.linkClass === LinkClass.PERMISSION && + resource.headUuid === allUsersGroupUuid && + resource.tailUuid === props.uuid + )(state.resources); + + if (user) { + return user.isActive ? { status: UserAccountStatus.ACTIVE } : permissions.length > 0 ? { status: UserAccountStatus.SETUP } : { status: UserAccountStatus.INACTIVE }; } else { - return ; + return { status: UserAccountStatus.UNKNOWN }; } } -export const ResourceIsActive = connect( - (state: RootState, props: { uuid: string }) => { - const resource = getResource(props.uuid)(state.resources); - return resource || { isActive: false, kind: ResourceKind.NONE }; - }, { toggleIsActive } -)(renderIsActive); - -export const ResourceLinkTailIsActive = connect( +export const ResourceLinkTailAccountStatus = connect( (state: RootState, props: { uuid: string }) => { const link = getResource(props.uuid)(state.resources); - const tailResource = getResource(link?.tailUuid || '')(state.resources); - - return tailResource || { isActive: false, kind: ResourceKind.NONE }; - }, { toggleIsActive } -)(renderIsActive); - -const renderIsHidden = (props: { memberLinkUuid: string, permissionLinkUuid: string, hidden: boolean, setMemberIsHidden: (memberLinkUuid: string, permissionLinkUuid: string, hide: boolean) => void }) => { + return link && link.tailKind === ResourceKind.USER ? getUserAccountStatus(state, { uuid: link.tailUuid }) : { status: UserAccountStatus.UNKNOWN }; + })(renderAccountStatus); + +export const UserResourceAccountStatus = connect(getUserAccountStatus)(renderAccountStatus); + +const renderIsHidden = (props: { + memberLinkUuid: string, + permissionLinkUuid: string, + visible: boolean, + canManage: boolean, + setMemberIsHidden: (memberLinkUuid: string, permissionLinkUuid: string, hide: boolean) => void +}) => { if (props.memberLinkUuid) { return props.setMemberIsHidden(props.memberLinkUuid, props.permissionLinkUuid, !props.hidden)} />; + data-cy="user-visible-checkbox" + color="primary" + checked={props.visible} + disabled={!props.canManage} + onClick={(e) => { + e.stopPropagation(); + props.setMemberIsHidden(props.memberLinkUuid, props.permissionLinkUuid, !props.visible); + }} />; } else { return ; } } -export const ResourceLinkTailIsHidden = connect( +export const ResourceLinkTailIsVisible = connect( (state: RootState, props: { uuid: string }) => { const link = getResource(props.uuid)(state.resources); const member = getResource(link?.tailUuid || '')(state.resources); @@ -240,10 +336,12 @@ export const ResourceLinkTailIsHidden = connect( const permissionLinkUuid = permissions.length > 0 ? permissions[0].uuid : ''; const isVisible = link && group && permissions.length > 0; + // Consider whether the current user canManage this resurce in addition when it's possible + const isBuiltin = isBuiltinGroup(link?.headUuid || ''); return member?.kind === ResourceKind.USER - ? { memberLinkUuid: link?.uuid, permissionLinkUuid, hidden: !isVisible } - : { memberLinkUuid: '', permissionLinkUuid: '', hidden: false}; + ? { memberLinkUuid: link?.uuid, permissionLinkUuid, visible: isVisible, canManage: !isBuiltin } + : { memberLinkUuid: '', permissionLinkUuid: '', visible: false, canManage: false }; }, { setMemberIsHidden } )(renderIsHidden); @@ -251,7 +349,10 @@ const renderIsAdmin = (props: { uuid: string, isAdmin: boolean, toggleIsAdmin: ( props.toggleIsAdmin(props.uuid)} />; + onClick={(e) => { + e.stopPropagation(); + props.toggleIsAdmin(props.uuid); + }} />; export const ResourceIsAdmin = connect( (state: RootState, props: { uuid: string }) => { @@ -260,15 +361,37 @@ export const ResourceIsAdmin = connect( }, { toggleIsAdmin } )(renderIsAdmin); -const renderUsername = (item: { username: string }) => - {item.username}; +const renderUsername = (item: { username: string, uuid: string }) => + {item.username || item.uuid}; export const ResourceUsername = connect( (state: RootState, props: { uuid: string }) => { const resource = getResource(props.uuid)(state.resources); - return resource || { username: '' }; + return resource || { username: '', uuid: props.uuid }; })(renderUsername); +// Virtual machine resource + +const renderHostname = (item: { hostname: string }) => + {item.hostname}; + +export const VirtualMachineHostname = connect( + (state: RootState, props: { uuid: string }) => { + const resource = getResource(props.uuid)(state.resources); + return resource || { hostname: '' }; + })(renderHostname); + +const renderVirtualMachineLogin = (login: { user: string }) => + {login.user} + +export const VirtualMachineLogin = connect( + (state: RootState, props: { linkUuid: string }) => { + const permission = getResource(props.linkUuid)(state.resources); + const user = getResource(permission?.tailUuid || '')(state.resources); + + return { user: user?.username || permission?.tailUuid || '' }; + })(renderVirtualMachineLogin); + // Common methods const renderCommonData = (data: string) => {data}; @@ -308,7 +431,7 @@ const clusterColors = [ export const ResourceCluster = (props: { uuid: string }) => { const CLUSTER_ID_LENGTH = 5; const pos = props.uuid.length > CLUSTER_ID_LENGTH ? props.uuid.indexOf('-') : 5; - const clusterId = pos >= CLUSTER_ID_LENGTH ? props.uuid.substr(0, pos) : ''; + const clusterId = pos >= CLUSTER_ID_LENGTH ? props.uuid.substring(0, pos) : ''; const ci = pos >= CLUSTER_ID_LENGTH ? ((((( (props.uuid.charCodeAt(0) * props.uuid.charCodeAt(1)) + props.uuid.charCodeAt(2)) @@ -343,7 +466,7 @@ export const ResourceLinkClass = connect( const getResourceDisplayName = (resource: Resource): string => { if ((resource as UserResource).kind === ResourceKind.USER - && typeof (resource as UserResource).firstName !== 'undefined') { + && typeof (resource as UserResource).firstName !== 'undefined') { // We can be sure the resource is UserResource return getUserDisplayName(resource as UserResource); } else { @@ -355,7 +478,7 @@ const renderResourceLink = (dispatch: Dispatch, item: Resource) => { var displayName = getResourceDisplayName(item); return dispatch(navigateTo(item.uuid))}> - {resourceLabel(item.kind)}: {displayName || item.uuid} + {resourceLabel(item.kind, item && item.kind === ResourceKind.GROUP ? (item as GroupResource).groupClass || '' : '')}: {displayName || item.uuid} ; }; @@ -417,19 +540,21 @@ const renderLinkDelete = (dispatch: Dispatch, item: LinkResource, canManage: boo ; } else { - return ; + return ; } } export const ResourceLinkDelete = connect( (state: RootState, props: { uuid: string }) => { const link = getResource(props.uuid)(state.resources); + const isBuiltin = isBuiltinGroup(link?.headUuid || '') || isBuiltinGroup(link?.tailUuid || ''); + return { item: link || { uuid: '', kind: ResourceKind.NONE }, - canManage: link && getResourceLinkCanManage(state, link), + canManage: link && getResourceLinkCanManage(state, link) && !isBuiltin, }; })((props: { item: LinkResource, canManage: boolean } & DispatchProp) => - renderLinkDelete(props.dispatch, props.item, props.canManage)); + renderLinkDelete(props.dispatch, props.item, props.canManage)); export const ResourceLinkTailEmail = connect( (state: RootState, props: { uuid: string }) => { @@ -451,7 +576,7 @@ const renderPermissionLevel = (dispatch: Dispatch, link: LinkResource, canManage return {formatPermissionLevel(link.name as PermissionLevel)} {canManage ? - dispatch(openPermissionEditContextMenu(event, link))}> + dispatch(openPermissionEditContextMenu(event, link))}> : '' @@ -462,10 +587,11 @@ const renderPermissionLevel = (dispatch: Dispatch, link: LinkResource, canManage export const ResourceLinkHeadPermissionLevel = connect( (state: RootState, props: { uuid: string }) => { const link = getResource(props.uuid)(state.resources); + const isBuiltin = isBuiltinGroup(link?.headUuid || '') || isBuiltinGroup(link?.tailUuid || ''); return { link: link || { uuid: '', name: '', kind: ResourceKind.NONE }, - canManage: link && getResourceLinkCanManage(state, link), + canManage: link && getResourceLinkCanManage(state, link) && !isBuiltin, }; })((props: { link: LinkResource, canManage: boolean } & DispatchProp) => renderPermissionLevel(props.dispatch, props.link, props.canManage)); @@ -473,10 +599,11 @@ export const ResourceLinkHeadPermissionLevel = connect( export const ResourceLinkTailPermissionLevel = connect( (state: RootState, props: { uuid: string }) => { const link = getResource(props.uuid)(state.resources); + const isBuiltin = isBuiltinGroup(link?.headUuid || '') || isBuiltinGroup(link?.tailUuid || ''); return { link: link || { uuid: '', name: '', kind: ResourceKind.NONE }, - canManage: link && getResourceLinkCanManage(state, link), + canManage: link && getResourceLinkCanManage(state, link) && !isBuiltin, }; })((props: { link: LinkResource, canManage: boolean } & DispatchProp) => renderPermissionLevel(props.dispatch, props.link, props.canManage)); @@ -538,17 +665,25 @@ export const ResourceWorkflowStatus = connect( }; })((props: { ownerUuid?: string, uuidPrefix: string }) => renderWorkflowStatus(props.uuidPrefix, props.ownerUuid)); -export const ResourceLastModifiedDate = connect( +const renderProcessState = (processState: string) => {processState || '-'} + +export const ResourceProcessState = connect( (state: RootState, props: { uuid: string }) => { - const resource = getResource(props.uuid)(state.resources); - return { date: resource ? resource.modifiedAt : '' }; - })((props: { date: string }) => renderDate(props.date)); + const resource = getResource(props.uuid)(state.resources); + return { state: resource?.state ? resource.state: '' }; + })((props: { state: string }) => renderProcessState(props.state)); export const ResourceCreatedAtDate = connect( (state: RootState, props: { uuid: string }) => { const resource = getResource(props.uuid)(state.resources); return { date: resource ? resource.createdAt : '' }; })((props: { date: string }) => renderDate(props.date)); + +export const ResourceLastModifiedDate = connect( + (state: RootState, props: { uuid: string }) => { + const resource = getResource(props.uuid)(state.resources); + return { date: resource ? resource.modifiedAt : '' }; + })((props: { date: string }) => renderDate(props.date)); export const ResourceTrashDate = connect( (state: RootState, props: { uuid: string }) => { @@ -580,7 +715,7 @@ export const ResourceFileSize = connect( const renderOwner = (owner: string) => - {owner} + {owner || '-'} ; export const ResourceOwner = connect( @@ -597,6 +732,60 @@ export const ResourceOwnerName = connect( return { owner: ownerName ? ownerName!.name : resource!.ownerUuid }; })((props: { owner: string }) => renderOwner(props.owner)); +export const ResourceUUID = connect( + (state: RootState, props: { uuid: string }) => { + const resource = getResource(props.uuid)(state.resources); + return { uuid: resource ? resource.uuid : '' }; + })((props: { uuid: string }) => renderUuid({uuid: props.uuid})); + +const renderPortableDataHash = (portableDataHash:string | null) => + + {portableDataHash ? <>{portableDataHash} + : '-' } + + +export const ResourcePortableDataHash = connect( + (state: RootState, props: { uuid: string }) => { + const resource = getResource(props.uuid)(state.resources); + // console.log('COLLECTION_RESOIRCE', resource) + return { portableDataHash: resource ? resource.portableDataHash : '' }; + })((props: { portableDataHash: string }) => renderPortableDataHash(props.portableDataHash)); + +const renderVersion = (version: number) =>{ + return {version ?? '-'} +} + +export const ResourceVersion = connect( + (state: RootState, props: { uuid: string }) => { + const resource = getResource(props.uuid)(state.resources); + return { version: resource ? resource.version: '' }; + })((props: { version: number }) => renderVersion(props.version)); + +const renderDescription = (description: string)=>{ + const truncatedDescription = description ? description.slice(0, 18) + '...' : '-' + return {truncatedDescription}; +} + +const renderFileCount = (fileCount: number) =>{ + return {fileCount ?? '-'} +} + +export const ResourceFileCount = connect( + (state: RootState, props: { uuid: string }) => { + const resource = getResource(props.uuid)(state.resources); + return { fileCount: resource ? resource.fileCount: '' }; + })((props: { fileCount: number }) => renderFileCount(props.fileCount)); + +export const ResourceDescription = connect( + (state: RootState, props: { uuid: string }) => { + const resource = getResource(props.uuid)(state.resources); + //testing--------------- + const containerRequestDescription = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + if (resource && !resource.description && resource.kind === ResourceKind.PROCESS) resource.description = containerRequestDescription + //testing--------------- + return { description: resource ? resource.description : '' }; + })((props: { description: string }) => renderDescription(props.description)); + const userFromID = connect( (state: RootState, props: { uuid: string }) => { @@ -610,10 +799,17 @@ const userFromID = return { uuid: props.uuid, userFullname }; }); -export const ResourceOwnerWithName = +const ownerFromResourceId = compose( - userFromID, - withStyles({}, { withTheme: true })) + connect((state: RootState, props: { uuid: string }) => { + const childResource = getResource(props.uuid)(state.resources); + return { uuid: childResource ? (childResource as Resource).ownerUuid : '' }; + }), + userFromID + ); + +const _resourceWithName = + withStyles({}, { withTheme: true }) ((props: { uuid: string, userFullname: string, dispatch: Dispatch, theme: ArvadosTheme }) => { const { uuid, userFullname, dispatch, theme } = props; @@ -629,9 +825,13 @@ export const ResourceOwnerWithName = ; }); +export const ResourceOwnerWithName = ownerFromResourceId(_resourceWithName); + +export const ResourceWithName = userFromID(_resourceWithName); + export const UserNameFromID = compose(userFromID)( - (props: { uuid: string, userFullname: string, dispatch: Dispatch }) => { + (props: { uuid: string, displayAsText?: string, userFullname: string, dispatch: Dispatch }) => { const { uuid, userFullname, dispatch } = props; if (userFullname === '') { @@ -695,7 +895,7 @@ export const ResponsiblePerson = return {responsiblePersonName} ({uuid}) - ; + ; }); const renderType = (type: string, subtype: string) => @@ -725,19 +925,36 @@ export const CollectionStatus = connect((state: RootState, props: { uuid: string : head version ); +export const CollectionName = connect((state: RootState, props: { uuid: string, className?: string }) => { + return { + collection: getResource(props.uuid)(state.resources), + uuid: props.uuid, + className: props.className, + }; +})((props: { collection: CollectionResource, uuid: string, className?: string }) => + {props.collection?.name || props.uuid} +); + export const ProcessStatus = compose( connect((state: RootState, props: { uuid: string }) => { return { process: getProcess(props.uuid)(state.resources) }; }), withStyles({}, { withTheme: true })) - ((props: { process?: Process, theme: ArvadosTheme }) => { - const status = props.process ? getProcessStatus(props.process) : "-"; - return - {status} - ; - }); + ((props: { process?: Process, theme: ArvadosTheme }) => + props.process + ? + : - + ); export const ProcessStartDate = connect( (state: RootState, props: { uuid: string }) => {