X-Git-Url: https://git.arvados.org/arvados-workbench2.git/blobdiff_plain/cdc8a73914399a401642ca553e9d3d8b2d42db5c..de10071369ab482d70bb63fe45df317d6bd3f6c5:/src/views-components/data-explorer/renderers.tsx diff --git a/src/views-components/data-explorer/renderers.tsx b/src/views-components/data-explorer/renderers.tsx index abf18392..47c1eaa9 100644 --- a/src/views-components/data-explorer/renderers.tsx +++ b/src/views-components/data-explorer/renderers.tsx @@ -2,106 +2,804 @@ // // SPDX-License-Identifier: AGPL-3.0 -import * as React from 'react'; -import { Grid, Typography } from '@material-ui/core'; -import { FavoriteStar } from '../favorite-star/favorite-star'; -import { ResourceKind } from '~/models/resource'; -import { ProjectIcon, CollectionIcon, ProcessIcon, DefaultIcon } from '~/components/icon/icon'; -import { formatDate, formatFileSize } from '~/common/formatters'; -import { resourceLabel } from '~/common/labels'; -import { connect } from 'react-redux'; -import { RootState } from '~/store/store'; -import { getResource } from '../../store/resources/resources'; -import { GroupContentsResource } from '~/services/groups-service/groups-service'; -import { ProcessResource } from '~/models/process'; - - -export const renderName = (item: { name: string; uuid: string, kind: string }) => - +import React from 'react'; +import { Grid, Typography, withStyles, Tooltip, IconButton, Checkbox } 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 { formatDate, formatFileSize, formatTime } from 'common/formatters'; +import { resourceLabel } from 'common/labels'; +import { connect, DispatchProp } from 'react-redux'; +import { RootState } from 'store/store'; +import { getResource, filterResources } from 'store/resources/resources'; +import { GroupContentsResource } from 'services/groups-service/groups-service'; +import { getProcess, Process, getProcessStatus, getProcessStatusColor, getProcessRuntime } from 'store/processes/process'; +import { ArvadosTheme } from 'common/custom-theme'; +import { compose, Dispatch } from 'redux'; +import { WorkflowResource } from 'models/workflow'; +import { ResourceStatus as WorkflowStatus } from 'views/workflow-panel/workflow-panel-view'; +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 { LinkClass, LinkResource } from 'models/link'; +import { navigateTo, navigateToGroupDetails } 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, 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'; + +const renderName = (dispatch: Dispatch, item: GroupContentsResource) => { + + const navFunc = ("groupClass" in item && item.groupClass === GroupClass.ROLE ? navigateToGroupDetails : navigateTo); + return {renderIcon(item)} - + dispatch(navFunc(item.uuid))}> + {item.kind === ResourceKind.PROJECT || item.kind === ResourceKind.COLLECTION + ? + : null} {item.name} + ; +}; export const ResourceName = connect( (state: RootState, props: { uuid: string }) => { - const resource = getResource(props.uuid)(state.resources) as GroupContentsResource | undefined; - return resource || { name: '', uuid: '', kind: '' }; - })(renderName); + const resource = getResource(props.uuid)(state.resources); + return resource; + })((resource: GroupContentsResource & DispatchProp) => renderName(resource.dispatch, resource)); -export const renderIcon = (item: { kind: string }) => { +const renderIcon = (item: GroupContentsResource) => { switch (item.kind) { case ResourceKind.PROJECT: + if (item.groupClass === GroupClass.FILTER) { + return ; + } return ; case ResourceKind.COLLECTION: - return ; + if (item.uuid === item.currentVersionUuid) { + return ; + } + return ; case ResourceKind.PROCESS: return ; + case ResourceKind.WORKFLOW: + return ; default: return ; } }; -export const renderDate = (date: string) => { - return {formatDate(date)}; +const renderDate = (date?: string) => { + return {formatDate(date)}; +}; + +const renderWorkflowName = (item: WorkflowResource) => + + + {renderIcon(item)} + + + + {item.name} + + + ; + +export const ResourceWorkflowName = connect( + (state: RootState, props: { uuid: string }) => { + const resource = getResource(props.uuid)(state.resources); + return resource; + })(renderWorkflowName); + +const getPublicUuid = (uuidPrefix: string) => { + return `${uuidPrefix}-tpzed-anonymouspublic`; +}; + +const resourceShare = (dispatch: Dispatch, uuidPrefix: string, ownerUuid?: string, uuid?: string) => { + const isPublic = ownerUuid === getPublicUuid(uuidPrefix); + return ( +
+ {!isPublic && uuid && + + dispatch(openSharingDialog(uuid))}> + + + + } +
+ ); +}; + +export const ResourceShare = connect( + (state: RootState, props: { uuid: string }) => { + const resource = getResource(props.uuid)(state.resources); + const uuidPrefix = getUuidPrefix(state); + return { + uuid: resource ? resource.uuid : '', + ownerUuid: resource ? resource.ownerUuid : '', + uuidPrefix + }; + })((props: { ownerUuid?: string, uuidPrefix: string, uuid?: string } & DispatchProp) => + resourceShare(props.dispatch, props.uuidPrefix, props.ownerUuid, props.uuid)); + +// User Resources +const renderFirstName = (item: { firstName: string }) => { + return {item.firstName}; +}; + +export const ResourceFirstName = connect( + (state: RootState, props: { uuid: string }) => { + const resource = getResource(props.uuid)(state.resources); + return resource || { firstName: '' }; + })(renderFirstName); + +const renderLastName = (item: { lastName: string }) => + {item.lastName}; + +export const ResourceLastName = connect( + (state: RootState, props: { uuid: string }) => { + const resource = getResource(props.uuid)(state.resources); + return resource || { lastName: '' }; + })(renderLastName); + +const renderFullName = (item: { firstName: string, lastName: string }) => + {(item.firstName + " " + item.lastName).trim()}; + +export const ResourceFullName = connect( + (state: RootState, props: { uuid: string }) => { + const resource = getResource(props.uuid)(state.resources); + return resource || { firstName: '', lastName: '' }; + })(renderFullName); + + +const renderUuid = (item: { uuid: string }) => + {item.uuid}; + +export const ResourceUuid = connect( + (state: RootState, props: { uuid: string }) => { + const resource = getResource(props.uuid)(state.resources); + return resource || { uuid: '' }; + })(renderUuid); + +const renderEmail = (item: { email: string }) => + {item.email}; + +export const ResourceEmail = connect( + (state: RootState, props: { uuid: string }) => { + const resource = getResource(props.uuid)(state.resources); + return resource || { email: '' }; + })(renderEmail); + +const renderIsActive = (props: { uuid: string, kind: ResourceKind, isActive: boolean, toggleIsActive: (uuid: string) => void, disabled?: boolean }) => { + if (props.kind === ResourceKind.USER) { + return props.toggleIsActive(props.uuid)} />; + } else { + return ; + } +} + +export const ResourceIsActive = connect( + (state: RootState, props: { uuid: string, disabled?: boolean }) => { + const resource = getResource(props.uuid)(state.resources); + return resource ? {...resource, disabled: !!props.disabled} : { isActive: false, kind: ResourceKind.NONE }; + }, { toggleIsActive } +)(renderIsActive); + +export const ResourceLinkTailIsActive = connect( + (state: RootState, props: { uuid: string, disabled?: boolean }) => { + const link = getResource(props.uuid)(state.resources); + const tailResource = getResource(link?.tailUuid || '')(state.resources); + + return tailResource ? {...tailResource, disabled: !!props.disabled} : { isActive: false, kind: ResourceKind.NONE }; + }, { toggleIsActive } +)(renderIsActive); + +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.visible)} />; + } else { + return ; + } +} + +export const ResourceLinkTailIsVisible = connect( + (state: RootState, props: { uuid: string }) => { + const link = getResource(props.uuid)(state.resources); + const member = getResource(link?.tailUuid || '')(state.resources); + const group = getResource(link?.headUuid || '')(state.resources); + const permissions = filterResources((resource: LinkResource) => { + return resource.linkClass === LinkClass.PERMISSION + && resource.headUuid === link?.tailUuid + && resource.tailUuid === group?.uuid + && resource.name === PermissionLevel.CAN_READ; + })(state.resources); + + 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, visible: isVisible, canManage: !isBuiltin } + : { memberLinkUuid: '', permissionLinkUuid: '', visible: false, canManage: false }; + }, { setMemberIsHidden } +)(renderIsHidden); + +const renderIsAdmin = (props: { uuid: string, isAdmin: boolean, toggleIsAdmin: (uuid: string) => void }) => + props.toggleIsAdmin(props.uuid)} />; + +export const ResourceIsAdmin = connect( + (state: RootState, props: { uuid: string }) => { + const resource = getResource(props.uuid)(state.resources); + return resource || { isAdmin: false }; + }, { toggleIsAdmin } +)(renderIsAdmin); + +const renderUsername = (item: { username: string }) => + {item.username}; + +export const ResourceUsername = connect( + (state: RootState, props: { uuid: string }) => { + const resource = getResource(props.uuid)(state.resources); + return resource || { username: '' }; + })(renderUsername); + +// Common methods +const renderCommonData = (data: string) => + {data}; + +const renderCommonDate = (date: string) => + {formatDate(date)}; + +export const CommonUuid = withResourceData('uuid', renderCommonData); + +// Api Client Authorizations +export const TokenApiClientId = withResourceData('apiClientId', renderCommonData); + +export const TokenApiToken = withResourceData('apiToken', renderCommonData); + +export const TokenCreatedByIpAddress = withResourceData('createdByIpAddress', renderCommonDate); + +export const TokenDefaultOwnerUuid = withResourceData('defaultOwnerUuid', renderCommonData); + +export const TokenExpiresAt = withResourceData('expiresAt', renderCommonDate); + +export const TokenLastUsedAt = withResourceData('lastUsedAt', renderCommonDate); + +export const TokenLastUsedByIpAddress = withResourceData('lastUsedByIpAddress', renderCommonData); + +export const TokenScopes = withResourceData('scopes', renderCommonData); + +export const TokenUserId = withResourceData('userId', renderCommonData); + +const clusterColors = [ + ['#f44336', '#fff'], + ['#2196f3', '#fff'], + ['#009688', '#fff'], + ['#cddc39', '#fff'], + ['#ff9800', '#fff'] +]; + +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.substring(0, pos) : ''; + const ci = pos >= CLUSTER_ID_LENGTH ? ((((( + (props.uuid.charCodeAt(0) * props.uuid.charCodeAt(1)) + + props.uuid.charCodeAt(2)) + * props.uuid.charCodeAt(3)) + + props.uuid.charCodeAt(4))) % clusterColors.length) : 0; + return {clusterId}; +}; + +// Links Resources +const renderLinkName = (item: { name: string }) => + {item.name || '(none)'}; + +export const ResourceLinkName = connect( + (state: RootState, props: { uuid: string }) => { + const resource = getResource(props.uuid)(state.resources); + return resource || { name: '' }; + })(renderLinkName); + +const renderLinkClass = (item: { linkClass: string }) => + {item.linkClass}; + +export const ResourceLinkClass = connect( + (state: RootState, props: { uuid: string }) => { + const resource = getResource(props.uuid)(state.resources); + return resource || { linkClass: '' }; + })(renderLinkClass); + +const getResourceDisplayName = (resource: Resource): string => { + if ((resource as UserResource).kind === ResourceKind.USER + && typeof (resource as UserResource).firstName !== 'undefined') { + // We can be sure the resource is UserResource + return getUserDisplayName(resource as UserResource); + } else { + return (resource as GroupContentsResource).name; + } +} + +const renderResourceLink = (dispatch: Dispatch, item: Resource) => { + var displayName = getResourceDisplayName(item); + + return dispatch(navigateTo(item.uuid))}> + {resourceLabel(item.kind, item && item.kind === ResourceKind.GROUP ? (item as GroupResource).groupClass || '' : '')}: {displayName || item.uuid} + ; +}; + +export const ResourceLinkTail = connect( + (state: RootState, props: { uuid: string }) => { + const resource = getResource(props.uuid)(state.resources); + const tailResource = getResource(resource?.tailUuid || '')(state.resources); + + return { + item: tailResource || { uuid: resource?.tailUuid || '', kind: resource?.tailKind || ResourceKind.NONE } + }; + })((props: { item: Resource } & DispatchProp) => + renderResourceLink(props.dispatch, props.item)); + +export const ResourceLinkHead = connect( + (state: RootState, props: { uuid: string }) => { + const resource = getResource(props.uuid)(state.resources); + const headResource = getResource(resource?.headUuid || '')(state.resources); + + return { + item: headResource || { uuid: resource?.headUuid || '', kind: resource?.headKind || ResourceKind.NONE } + }; + })((props: { item: Resource } & DispatchProp) => + renderResourceLink(props.dispatch, props.item)); + +export const ResourceLinkUuid = connect( + (state: RootState, props: { uuid: string }) => { + const resource = getResource(props.uuid)(state.resources); + return resource || { uuid: '' }; + })(renderUuid); + +export const ResourceLinkHeadUuid = connect( + (state: RootState, props: { uuid: string }) => { + const link = getResource(props.uuid)(state.resources); + const headResource = getResource(link?.headUuid || '')(state.resources); + + return headResource || { uuid: '' }; + })(renderUuid); + +export const ResourceLinkTailUuid = connect( + (state: RootState, props: { uuid: string }) => { + const link = getResource(props.uuid)(state.resources); + const tailResource = getResource(link?.tailUuid || '')(state.resources); + + return tailResource || { uuid: '' }; + })(renderUuid); + +const renderLinkDelete = (dispatch: Dispatch, item: LinkResource, canManage: boolean) => { + if (item.uuid) { + return canManage ? + + dispatch(openRemoveGroupMemberDialog(item.uuid))}> + + + : + + + + + ; + } else { + 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) && !isBuiltin, + }; + })((props: { item: LinkResource, canManage: boolean } & DispatchProp) => + renderLinkDelete(props.dispatch, props.item, props.canManage)); + +export const ResourceLinkTailEmail = connect( + (state: RootState, props: { uuid: string }) => { + const link = getResource(props.uuid)(state.resources); + const resource = getResource(link?.tailUuid || '')(state.resources); + + return resource || { email: '' }; + })(renderEmail); + +export const ResourceLinkTailUsername = connect( + (state: RootState, props: { uuid: string }) => { + const link = getResource(props.uuid)(state.resources); + const resource = getResource(link?.tailUuid || '')(state.resources); + + return resource || { username: '' }; + })(renderUsername); + +const renderPermissionLevel = (dispatch: Dispatch, link: LinkResource, canManage: boolean) => { + return + {formatPermissionLevel(link.name as PermissionLevel)} + {canManage ? + dispatch(openPermissionEditContextMenu(event, link))}> + + : + '' + } + ; +} + +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) && !isBuiltin, + }; + })((props: { link: LinkResource, canManage: boolean } & DispatchProp) => + renderPermissionLevel(props.dispatch, props.link, props.canManage)); + +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) && !isBuiltin, + }; + })((props: { link: LinkResource, canManage: boolean } & DispatchProp) => + renderPermissionLevel(props.dispatch, props.link, props.canManage)); + +const getResourceLinkCanManage = (state: RootState, link: LinkResource) => { + const headResource = getResource(link.headUuid)(state.resources); + // const tailResource = getResource(link.tailUuid)(state.resources); + const userUuid = getUserUuid(state); + + if (headResource && headResource.kind === ResourceKind.GROUP) { + return userUuid ? (headResource as GroupResource).writableBy?.includes(userUuid) : false; + } else { + // true for now + return true; + } +} + +// Process Resources +const resourceRunProcess = (dispatch: Dispatch, uuid: string) => { + return ( +
+ {uuid && + + dispatch(openRunProcess(uuid))}> + + + } +
+ ); +}; + +export const ResourceRunProcess = connect( + (state: RootState, props: { uuid: string }) => { + const resource = getResource(props.uuid)(state.resources); + return { + uuid: resource ? resource.uuid : '' + }; + })((props: { uuid: string } & DispatchProp) => + resourceRunProcess(props.dispatch, props.uuid)); + +const renderWorkflowStatus = (uuidPrefix: string, ownerUuid?: string) => { + if (ownerUuid === getPublicUuid(uuidPrefix)) { + return renderStatus(WorkflowStatus.PUBLIC); + } else { + return renderStatus(WorkflowStatus.PRIVATE); + } }; +const renderStatus = (status: string) => + {status}; + +export const ResourceWorkflowStatus = connect( + (state: RootState, props: { uuid: string }) => { + const resource = getResource(props.uuid)(state.resources); + const uuidPrefix = getUuidPrefix(state); + return { + ownerUuid: resource ? resource.ownerUuid : '', + uuidPrefix + }; + })((props: { ownerUuid?: string, uuidPrefix: string }) => renderWorkflowStatus(props.uuidPrefix, props.ownerUuid)); + export const ResourceLastModifiedDate = connect( (state: RootState, props: { uuid: string }) => { - const resource = getResource(props.uuid)(state.resources) as GroupContentsResource | undefined; + const resource = getResource(props.uuid)(state.resources); return { date: resource ? resource.modifiedAt : '' }; })((props: { date: string }) => renderDate(props.date)); +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 ResourceTrashDate = connect( + (state: RootState, props: { uuid: string }) => { + const resource = getResource(props.uuid)(state.resources); + return { date: resource ? resource.trashAt : '' }; + })((props: { date: string }) => renderDate(props.date)); + +export const ResourceDeleteDate = connect( + (state: RootState, props: { uuid: string }) => { + const resource = getResource(props.uuid)(state.resources); + return { date: resource ? resource.deleteAt : '' }; + })((props: { date: string }) => renderDate(props.date)); + export const renderFileSize = (fileSize?: number) => - + {formatFileSize(fileSize)} ; export const ResourceFileSize = connect( (state: RootState, props: { uuid: string }) => { - const resource = getResource(props.uuid)(state.resources) as GroupContentsResource | undefined; - return {}; + const resource = getResource(props.uuid)(state.resources); + + if (resource && resource.kind !== ResourceKind.COLLECTION) { + return { fileSize: '' }; + } + + return { fileSize: resource ? resource.fileSizeTotal : 0 }; })((props: { fileSize?: number }) => renderFileSize(props.fileSize)); -export const renderOwner = (owner: string) => - +const renderOwner = (owner: string) => + {owner} ; export const ResourceOwner = connect( (state: RootState, props: { uuid: string }) => { - const resource = getResource(props.uuid)(state.resources) as GroupContentsResource | undefined; + const resource = getResource(props.uuid)(state.resources); return { owner: resource ? resource.ownerUuid : '' }; })((props: { owner: string }) => renderOwner(props.owner)); -export const renderType = (type: string) => +export const ResourceOwnerName = connect( + (state: RootState, props: { uuid: string }) => { + const resource = getResource(props.uuid)(state.resources); + const ownerNameState = state.ownerName; + const ownerName = ownerNameState.find(it => it.uuid === resource!.ownerUuid); + return { owner: ownerName ? ownerName!.name : resource!.ownerUuid }; + })((props: { owner: string }) => renderOwner(props.owner)); + +const userFromID = + connect( + (state: RootState, props: { uuid: string }) => { + let userFullname = ''; + const resource = getResource(props.uuid)(state.resources); + + if (resource) { + userFullname = getUserFullname(resource as User) || (resource as GroupContentsResource).name; + } + + return { uuid: props.uuid, userFullname }; + }); + +export const ResourceOwnerWithName = + compose( + userFromID, + withStyles({}, { withTheme: true })) + ((props: { uuid: string, userFullname: string, dispatch: Dispatch, theme: ArvadosTheme }) => { + const { uuid, userFullname, dispatch, theme } = props; + + if (userFullname === '') { + dispatch(loadResource(uuid, false)); + return + {uuid} + ; + } + + return + {userFullname} ({uuid}) + ; + }); + +export const UserNameFromID = + compose(userFromID)( + (props: { uuid: string, userFullname: string, dispatch: Dispatch }) => { + const { uuid, userFullname, dispatch } = props; + + if (userFullname === '') { + dispatch(loadResource(uuid, false)); + } + return + {userFullname ? userFullname : uuid} + ; + }); + +export const ResponsiblePerson = + compose( + connect( + (state: RootState, props: { uuid: string, parentRef: HTMLElement | null }) => { + let responsiblePersonName: string = ''; + let responsiblePersonUUID: string = ''; + let responsiblePersonProperty: string = ''; + + if (state.auth.config.clusterConfig.Collections.ManagedProperties) { + let index = 0; + const keys = Object.keys(state.auth.config.clusterConfig.Collections.ManagedProperties); + + while (!responsiblePersonProperty && keys[index]) { + const key = keys[index]; + if (state.auth.config.clusterConfig.Collections.ManagedProperties[key].Function === 'original_owner') { + responsiblePersonProperty = key; + } + index++; + } + } + + let resource: Resource | undefined = getResource(props.uuid)(state.resources); + + while (resource && resource.kind !== ResourceKind.USER && responsiblePersonProperty) { + responsiblePersonUUID = (resource as CollectionResource).properties[responsiblePersonProperty]; + resource = getResource(responsiblePersonUUID)(state.resources); + } + + if (resource && resource.kind === ResourceKind.USER) { + responsiblePersonName = getUserFullname(resource as UserResource) || (resource as GroupContentsResource).name; + } + + return { uuid: responsiblePersonUUID, responsiblePersonName, parentRef: props.parentRef }; + }), + withStyles({}, { withTheme: true })) + ((props: { uuid: string | null, responsiblePersonName: string, parentRef: HTMLElement | null, theme: ArvadosTheme }) => { + const { uuid, responsiblePersonName, parentRef, theme } = props; + + if (!uuid && parentRef) { + parentRef.style.display = 'none'; + return null; + } else if (parentRef) { + parentRef.style.display = 'block'; + } + + if (!responsiblePersonName) { + return + {uuid} + ; + } + + return + {responsiblePersonName} ({uuid}) + ; + }); + +const renderType = (type: string, subtype: string) => - {resourceLabel(type)} + {resourceLabel(type, subtype)} ; export const ResourceType = connect( (state: RootState, props: { uuid: string }) => { - const resource = getResource(props.uuid)(state.resources) as GroupContentsResource | undefined; - return { type: resource ? resource.kind : '' }; - })((props: { type: string }) => renderType(props.type)); + const resource = getResource(props.uuid)(state.resources); + return { type: resource ? resource.kind : '', subtype: resource && resource.kind === ResourceKind.GROUP ? resource.groupClass : '' }; + })((props: { type: string, subtype: string }) => renderType(props.type, props.subtype)); -export const renderStatus = (item: { status?: string }) => - - {item.status || "-"} - ; +export const ResourceStatus = connect((state: RootState, props: { uuid: string }) => { + return { resource: getResource(props.uuid)(state.resources) }; +})((props: { resource: GroupContentsResource }) => + (props.resource && props.resource.kind === ResourceKind.COLLECTION) + ? + : +); -export const ProcessStatus = connect( +export const CollectionStatus = connect((state: RootState, props: { uuid: string }) => { + return { collection: getResource(props.uuid)(state.resources) }; +})((props: { collection: CollectionResource }) => + (props.collection.uuid !== props.collection.currentVersionUuid) + ? version {props.collection.version} + : head version +); + +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} + ; + }); + +export const ProcessStartDate = connect( (state: RootState, props: { uuid: string }) => { - const resource = getResource(props.uuid)(state.resources) as ProcessResource | undefined; - return { status: resource ? resource.state : '-' }; - })((props: { status: string }) => renderType(props.status)); + const process = getProcess(props.uuid)(state.resources); + return { date: (process && process.container) ? process.container.startedAt : '' }; + })((props: { date: string }) => renderDate(props.date)); + +export const renderRunTime = (time: number) => + + {formatTime(time, true)} + ; + +interface ContainerRunTimeProps { + process: Process; +} + +interface ContainerRunTimeState { + runtime: number; +} + +export const ContainerRunTime = connect((state: RootState, props: { uuid: string }) => { + return { process: getProcess(props.uuid)(state.resources) }; +})(class extends React.Component { + private timer: any; + + constructor(props: ContainerRunTimeProps) { + super(props); + this.state = { runtime: this.getRuntime() }; + } + + getRuntime() { + return this.props.process ? getProcessRuntime(this.props.process) : 0; + } + + updateRuntime() { + this.setState({ runtime: this.getRuntime() }); + } + + componentDidMount() { + this.timer = setInterval(this.updateRuntime.bind(this), 5000); + } + + componentWillUnmount() { + clearInterval(this.timer); + } + + render() { + return renderRunTime(this.state.runtime); + } +});