X-Git-Url: https://git.arvados.org/arvados-workbench2.git/blobdiff_plain/450946f9f2192f807238cd0b020164896e0bef0d..4f88234088269faa064da9459376c3af2a740a7d:/src/views/process-panel/process-io-card.tsx diff --git a/src/views/process-panel/process-io-card.tsx b/src/views/process-panel/process-io-card.tsx index e1ab90cb..2857aa13 100644 --- a/src/views/process-panel/process-io-card.tsx +++ b/src/views/process-panel/process-io-card.tsx @@ -21,12 +21,11 @@ import { TableRow, TableCell, Paper, - Link, Grid, Chip, } from '@material-ui/core'; import { ArvadosTheme } from 'common/custom-theme'; -import { CloseIcon, InfoIcon, ProcessIcon } from 'components/icon/icon'; +import { CloseIcon, InfoIcon, ProcessIcon, InputIcon, OutputIcon } from 'components/icon/icon'; import { MPVPanelProps } from 'components/multi-panel-view/multi-panel-view'; import { BooleanCommandInputParameter, @@ -53,8 +52,15 @@ import { getInlineFileUrl } from 'views-components/context-menu/actions/helpers' import { AuthState } from 'store/auth/auth-reducer'; import mime from 'mime'; import { DefaultView } from 'components/default-view/default-view'; +import { getNavUrl } from 'routes/routes'; +import { Link as RouterLink } from 'react-router-dom'; +import { Link as MuiLink } from '@material-ui/core'; +import { InputCollectionMount } from 'store/processes/processes-actions'; +import { connect } from 'react-redux'; +import { RootState } from 'store/store'; +import { ProcessOutputCollectionFiles } from './process-output-collection-files'; -type CssRules = 'card' | 'content' | 'title' | 'header' | 'avatar' | 'iconHeader' | 'tableWrapper' | 'tableRoot' | 'paramValue' | 'keepLink' | 'imagePreview' | 'valArray'; +type CssRules = 'card' | 'content' | 'title' | 'header' | 'avatar' | 'iconHeader' | 'tableWrapper' | 'tableRoot' | 'paramValue' | 'keepLink' | 'imagePreview' | 'valArray' | 'emptyValue'; const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ card: { @@ -91,36 +97,54 @@ const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ }, paramValue: { display: 'flex', - alignItems: 'center', + alignItems: 'flex-start', + flexDirection: 'column', }, keepLink: { + color: theme.palette.primary.main, + textDecoration: 'none', + overflowWrap: 'break-word', cursor: 'pointer', }, imagePreview: { maxHeight: '15em', - marginRight: theme.spacing.unit, + maxWidth: '15em', + marginBottom: theme.spacing.unit, }, valArray: { display: 'flex', gap: '10px', flexWrap: 'wrap', + '& span': { + display: 'inline', + } + }, + emptyValue: { + color: theme.customs.colors.grey500, }, }); +export enum ProcessIOCardType { + INPUT = 'Inputs', + OUTPUT = 'Outputs', +} export interface ProcessIOCardDataProps { - label: string; + label: ProcessIOCardType; params: ProcessIOParameter[]; raw?: any; + mounts?: InputCollectionMount[]; + outputUuid?: string; } type ProcessIOCardProps = ProcessIOCardDataProps & WithStyles & MPVPanelProps; export const ProcessIOCard = withStyles(styles)( - ({ classes, label, params, raw, doHidePanel, panelName }: ProcessIOCardProps) => { + ({ classes, label, params, raw, mounts, outputUuid, doHidePanel, panelName }: ProcessIOCardProps) => { const [tabState, setTabState] = useState(0); const handleChange = (event: React.MouseEvent, value: number) => { setTabState(value); } + const PanelIcon = label == ProcessIOCardType.INPUT ? InputIcon : OutputIcon; return } + avatar={} title={ {label} @@ -144,21 +168,32 @@ export const ProcessIOCard = withStyles(styles)( } /> - {params.length ?
- - + + + {label === ProcessIOCardType.INPUT && } + {label === ProcessIOCardType.OUTPUT && } {tabState === 0 &&
- + {params.length ? + : + + + }
} {tabState === 1 &&
- + {params.length ? + : + + + } +
} + {tabState === 2 &&
+ {label === ProcessIOCardType.INPUT && } + {label === ProcessIOCardType.OUTPUT && }
} -
: - - } +
; } @@ -166,7 +201,6 @@ export const ProcessIOCard = withStyles(styles)( export type ProcessIOValue = { display: ReactElement; - nav?: string; imageUrl?: string; } @@ -184,7 +218,7 @@ type ProcessIOPreviewProps = ProcessIOPreviewDataProps & WithStyles; const ProcessIOPreview = withStyles(styles)( ({ classes, data }: ProcessIOPreviewProps) => - +
Label @@ -202,12 +236,9 @@ const ProcessIOPreview = withStyles(styles)( {param.value.map(val => ( {val.imageUrl ? Inline Preview : ""} - {val.nav ? - handleClick(val.nav)}>{val.display} - : - {val.display} - - } + + {val.display} + ))} ; @@ -229,55 +260,127 @@ const ProcessIORaw = withStyles(styles)( ); +interface ProcessInputMountsDataProps { + mounts: InputCollectionMount[]; +} + +type ProcessInputMountsProps = ProcessInputMountsDataProps & WithStyles; + +const ProcessInputMounts = withStyles(styles)(connect((state: RootState) => ({ + auth: state.auth, +}))(({ mounts, classes, auth }: ProcessInputMountsProps & { auth: AuthState }) => ( +
+ + + Path + Portable Data Hash + + + + {mounts.map(mount => ( + +
{mount.path}
+ + {mount.pdh} + +
+ ))} +
+
+))); + type FileWithSecondaryFiles = { secondaryFiles: File[]; } -export const getInputDisplayValue = (auth: AuthState, input: CommandInputParameter | CommandOutputParameter, pdh?: string): ProcessIOValue[] => { +export const getIOParamDisplayValue = (auth: AuthState, input: CommandInputParameter | CommandOutputParameter, pdh?: string): ProcessIOValue[] => { switch (true) { case isPrimitiveOfType(input, CWLType.BOOLEAN): - return [{display:
{String((input as BooleanCommandInputParameter).value)}
}]; + const boolValue = (input as BooleanCommandInputParameter).value; + + return boolValue !== undefined && + !(Array.isArray(boolValue) && boolValue.length === 0) ? + [{display:
{String(boolValue)}
}] : + [{display: }]; case isPrimitiveOfType(input, CWLType.INT): case isPrimitiveOfType(input, CWLType.LONG): - return [{display:
{String((input as IntCommandInputParameter).value)}
}]; + const intValue = (input as IntCommandInputParameter).value; + + return intValue !== undefined && + // Missing values are empty array + !(Array.isArray(intValue) && intValue.length === 0) ? + [{display:
{String(intValue)}
}] + : [{display: }]; case isPrimitiveOfType(input, CWLType.FLOAT): case isPrimitiveOfType(input, CWLType.DOUBLE): - return [{display:
{String((input as FloatCommandInputParameter).value)}
}]; + const floatValue = (input as FloatCommandInputParameter).value; + + return floatValue !== undefined && + !(Array.isArray(floatValue) && floatValue.length === 0) ? + [{display:
{String(floatValue)}
}]: + [{display: }]; case isPrimitiveOfType(input, CWLType.STRING): - return [{display:
{(input as StringCommandInputParameter).value || ""}
}]; + const stringValue = (input as StringCommandInputParameter).value || undefined; + + return stringValue !== undefined && + !(Array.isArray(stringValue) && stringValue.length === 0) ? + [{display:
{stringValue}
}] : + [{display: }]; case isPrimitiveOfType(input, CWLType.FILE): const mainFile = (input as FileCommandInputParameter).value; // secondaryFiles: File[] is not part of CommandOutputParameter so we cast to access secondaryFiles const secondaryFiles = ((mainFile as unknown) as FileWithSecondaryFiles)?.secondaryFiles || []; const files = [ - ...(mainFile ? [mainFile] : []), + ...(mainFile && !(Array.isArray(mainFile) && mainFile.length === 0) ? [mainFile] : []), ...secondaryFiles ]; - return files.map(file => fileToProcessIOValue(file, auth, pdh)); + + return files.length ? + files.map(file => fileToProcessIOValue(file, auth, pdh)) : + [{display: }]; case isPrimitiveOfType(input, CWLType.DIRECTORY): const directory = (input as DirectoryCommandInputParameter).value; - return directory ? [directoryToProcessIOValue(directory, auth, pdh)] : []; + + return directory !== undefined && + !(Array.isArray(directory) && directory.length === 0) ? + [directoryToProcessIOValue(directory, auth, pdh)] : + [{display: }]; case typeof input.type === 'object' && !(input.type instanceof Array) && input.type.type === 'enum': - return [{ display:
{(input as EnumCommandInputParameter).value || ''}
}]; + const enumValue = (input as EnumCommandInputParameter).value; + + return enumValue !== undefined ? + [{ display:
{(input as EnumCommandInputParameter).value || ''}
}] : + [{display: }]; case isArrayOfType(input, CWLType.STRING): - return [{ display: <>{((input as StringArrayCommandInputParameter).value || []).map((val) => )} }]; + const strArray = (input as StringArrayCommandInputParameter).value || []; + return strArray.length ? + [{ display: <>{((input as StringArrayCommandInputParameter).value || []).map((val) => )} }] : + [{display: }]; case isArrayOfType(input, CWLType.INT): case isArrayOfType(input, CWLType.LONG): - return [{ display: <>{((input as IntArrayCommandInputParameter).value || []).map((val) => )} }]; + const intArray = (input as IntArrayCommandInputParameter).value || []; + + return intArray.length ? + [{ display: <>{((input as IntArrayCommandInputParameter).value || []).map((val) => )} }] : + [{display: }]; case isArrayOfType(input, CWLType.FLOAT): case isArrayOfType(input, CWLType.DOUBLE): - return [{ display: <>{((input as FloatArrayCommandInputParameter).value || []).map((val) => )} }]; + const floatArray = (input as FloatArrayCommandInputParameter).value || []; + + return floatArray.length ? + [{ display: <>{floatArray.map((val) => )} }] : + [{display: }]; case isArrayOfType(input, CWLType.FILE): const fileArrayMainFile = ((input as FileArrayCommandInputParameter).value || []); @@ -290,12 +393,16 @@ export const getInputDisplayValue = (auth: AuthState, input: CommandInputParamet ...fileArraySecondaryFiles ]; - return fileArrayFiles - .map(file => fileToProcessIOValue(file, auth, pdh)); + return fileArrayFiles.length ? + fileArrayFiles.map(file => fileToProcessIOValue(file, auth, pdh)) : + [{display: }]; case isArrayOfType(input, CWLType.DIRECTORY): const directories = (input as DirectoryArrayCommandInputParameter).value || []; - return directories.map(directory => directoryToProcessIOValue(directory, auth, pdh)); + + return directories.length ? + directories.map(directory => directoryToProcessIOValue(directory, auth, pdh)) : + [{display: }]; default: return []; @@ -308,7 +415,34 @@ const getKeepUrl = (file: File | Directory, pdh?: string): string => { return keepUrl || ''; }; -const getNavUrl = (auth: AuthState, file: File | Directory, pdh?: string): string => { +interface KeepUrlProps { + auth: AuthState; + res: File | Directory; + pdh?: string; +} + +const KeepUrlBase = withStyles(styles)(({auth, res, pdh, classes}: KeepUrlProps & WithStyles) => { + const keepUrl = getKeepUrl(res, pdh); + const pdhUrl = keepUrl ? keepUrl.split('/').slice(0, 1)[0] : ''; + // Passing a pdh always returns a relative wb2 collection url + const pdhWbPath = getNavUrl(pdhUrl.replace('keep:', ''), auth); + return pdhUrl && pdhWbPath ? + {pdhUrl} : + <>; +}); + +const KeepUrlPath = withStyles(styles)(({auth, res, pdh, classes}: KeepUrlProps & WithStyles) => { + const keepUrl = getKeepUrl(res, pdh); + const keepUrlParts = keepUrl ? keepUrl.split('/') : []; + const keepUrlPath = keepUrlParts.length > 1 ? keepUrlParts.slice(1).join('/') : ''; + + const keepUrlPathNav = getKeepNavUrl(auth, res, pdh); + return keepUrlPath && keepUrlPathNav ? + handleClick(keepUrlPathNav)}>{keepUrlPath} : + <>; +}); + +const getKeepNavUrl = (auth: AuthState, file: File | Directory, pdh?: string): string => { let keepUrl = getKeepUrl(file, pdh).replace('keep:', ''); return (getInlineFileUrl(`${auth.config.keepWebServiceUrl}/c=${keepUrl}?api_token=${auth.apiToken}`, auth.config.keepWebServiceUrl, auth.config.keepWebInlineServiceUrl)); }; @@ -335,13 +469,21 @@ const normalizeDirectoryLocation = (directory: Directory): Directory => { const directoryToProcessIOValue = (directory: Directory, auth: AuthState, pdh?: string): ProcessIOValue => { const normalizedDirectory = normalizeDirectoryLocation(directory); return { - display: <>{getKeepUrl(normalizedDirectory, pdh)}, - nav: getNavUrl(auth, normalizedDirectory, pdh), + display: + / + , }; }; -const fileToProcessIOValue = (file: File, auth: AuthState, pdh?: string): ProcessIOValue => ({ - display: <>{getKeepUrl(file, pdh)}, - nav: getNavUrl(auth, file, pdh), - imageUrl: isFileImage(file.basename) ? getImageUrl(auth, file, pdh) : undefined, -}); +const fileToProcessIOValue = (file: File, auth: AuthState, pdh?: string): ProcessIOValue => { + return { + display: + / + , + imageUrl: isFileImage(file.basename) ? getImageUrl(auth, file, pdh) : undefined, + } +}; + +const EmptyValue = withStyles(styles)( + ({classes}: WithStyles) => No value +);