1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
5 import React, { useState } from 'react';
25 } from '@material-ui/core';
26 import { ArvadosTheme } from 'common/custom-theme';
27 import { CloseIcon, ProcessIcon } from 'components/icon/icon';
28 import { MPVPanelProps } from 'components/multi-panel-view/multi-panel-view';
30 BooleanCommandInputParameter,
31 CommandInputParameter,
34 DirectoryArrayCommandInputParameter,
35 DirectoryCommandInputParameter,
36 EnumCommandInputParameter,
37 FileArrayCommandInputParameter,
38 FileCommandInputParameter,
39 FloatArrayCommandInputParameter,
40 FloatCommandInputParameter,
41 IntArrayCommandInputParameter,
42 IntCommandInputParameter,
45 StringArrayCommandInputParameter,
46 StringCommandInputParameter,
47 } from "models/workflow";
48 import { CommandOutputParameter } from 'cwlts/mappings/v1.0/CommandOutputParameter';
49 import { File } from 'models/workflow';
50 import { getInlineFileUrl } from 'views-components/context-menu/actions/helpers';
51 import { AuthState } from 'store/auth/auth-reducer';
52 import mime from 'mime';
54 type CssRules = 'card' | 'content' | 'title' | 'header' | 'avatar' | 'iconHeader' | 'tableWrapper' | 'tableRoot' | 'paramValue' | 'keepLink' | 'imagePreview';
56 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
61 paddingTop: theme.spacing.unit,
62 paddingBottom: theme.spacing.unit,
66 color: theme.customs.colors.green700,
69 alignSelf: 'flex-start',
70 paddingTop: theme.spacing.unit * 0.5
73 padding: theme.spacing.unit * 1.0,
74 paddingTop: theme.spacing.unit * 0.5,
76 paddingBottom: theme.spacing.unit * 1,
81 paddingTop: theme.spacing.unit * 0.5
101 export interface ProcessIOCardDataProps {
103 params: ProcessIOParameter[];
107 type ProcessIOCardProps = ProcessIOCardDataProps & WithStyles<CssRules> & MPVPanelProps;
109 export const ProcessIOCard = withStyles(styles)(
110 ({ classes, label, params, raw, doHidePanel, panelName }: ProcessIOCardProps) => {
111 const [tabState, setTabState] = useState(0);
112 const handleChange = (event: React.MouseEvent<HTMLElement>, value: number) => {
116 return <Card className={classes.card}>
118 className={classes.header}
120 content: classes.title,
121 avatar: classes.avatar,
123 avatar={<ProcessIcon className={classes.iconHeader} />}
125 <Typography noWrap variant='h6' color='inherit'>
132 <Tooltip title={`Close ${panelName || 'panel'}`} disableFocusListener>
133 <IconButton onClick={doHidePanel}><CloseIcon /></IconButton>
137 <CardContent className={classes.content}>
138 <Tabs value={tabState} onChange={handleChange} variant="fullWidth">
139 <Tab label="Preview" />
142 {tabState === 0 && <div className={classes.tableWrapper}>
143 <ProcessIOPreview data={params} />
145 {tabState === 1 && <div className={classes.tableWrapper}>
146 <ProcessIORaw data={raw || params} />
153 export type ProcessIOValue = {
159 export type ProcessIOParameter = {
162 value: ProcessIOValue[];
165 interface ProcessIOPreviewDataProps {
166 data: ProcessIOParameter[];
169 type ProcessIOPreviewProps = ProcessIOPreviewDataProps & WithStyles<CssRules>;
171 const ProcessIOPreview = withStyles(styles)(
172 ({ classes, data }: ProcessIOPreviewProps) =>
173 <Table className={classes.tableRoot} aria-label="simple table">
176 <TableCell>Label</TableCell>
177 <TableCell>Description</TableCell>
178 <TableCell>Value</TableCell>
182 {data.map((param: ProcessIOParameter) => {
183 return <TableRow key={param.id}>
184 <TableCell component="th" scope="row">
187 <TableCell>{param.doc}</TableCell>
188 <TableCell>{param.value.map(val => (
189 <Typography className={classes.paramValue}>
190 {val.imageUrl ? <img className={classes.imagePreview} src={val.imageUrl} alt="Inline Preview" /> : ""}
191 {val.nav ? <Link className={classes.keepLink} onClick={() => handleClick(val.nav)}>{val.display}</Link> : val.display}
200 const handleClick = (url) => {
201 window.open(url, '_blank');
204 const ProcessIORaw = withStyles(styles)(
205 ({ data }: ProcessIOPreviewProps) =>
206 <Paper elevation={0}>
208 {JSON.stringify(data, null, 2)}
213 // secondaryFiles File[] is not part of CommandOutputParameter so we pass in an extra param
214 export const getInputDisplayValue = (auth: AuthState, input: CommandInputParameter | CommandOutputParameter, pdh?: string, secondaryFiles: File[] = []): ProcessIOValue[] => {
216 case isPrimitiveOfType(input, CWLType.BOOLEAN):
217 return [{display: String((input as BooleanCommandInputParameter).value)}];
219 case isPrimitiveOfType(input, CWLType.INT):
220 case isPrimitiveOfType(input, CWLType.LONG):
221 return [{display: String((input as IntCommandInputParameter).value)}];
223 case isPrimitiveOfType(input, CWLType.FLOAT):
224 case isPrimitiveOfType(input, CWLType.DOUBLE):
225 return [{display: String((input as FloatCommandInputParameter).value)}];
227 case isPrimitiveOfType(input, CWLType.STRING):
228 return [{display: (input as StringCommandInputParameter).value || ""}];
230 case isPrimitiveOfType(input, CWLType.FILE):
231 const mainFile = (input as FileCommandInputParameter).value;
233 ...(mainFile ? [mainFile] : []),
237 return files.map(file => ({
238 display: getKeepUrl(file, pdh),
239 nav: getNavUrl(auth, file, pdh),
240 imageUrl: isFileImage(file.basename) ? getImageUrl(auth, file, pdh) : undefined,
243 case isPrimitiveOfType(input, CWLType.DIRECTORY):
244 const directory = (input as DirectoryCommandInputParameter).value;
245 return directory ? [directoryToProcessIOValue(directory, auth, pdh)] : [];
247 case typeof input.type === 'object' &&
248 !(input.type instanceof Array) &&
249 input.type.type === 'enum':
250 return [{ display: (input as EnumCommandInputParameter).value || '' }];
252 case isArrayOfType(input, CWLType.STRING):
253 return [{ display: ((input as StringArrayCommandInputParameter).value || []).join(', ') }];
255 case isArrayOfType(input, CWLType.INT):
256 case isArrayOfType(input, CWLType.LONG):
257 return [{ display: ((input as IntArrayCommandInputParameter).value || []).join(', ') }];
259 case isArrayOfType(input, CWLType.FLOAT):
260 case isArrayOfType(input, CWLType.DOUBLE):
261 return [{ display: ((input as FloatArrayCommandInputParameter).value || []).join(', ') }];
263 case isArrayOfType(input, CWLType.FILE):
264 return ((input as FileArrayCommandInputParameter).value || []).map(file => ({
265 display: getKeepUrl(file, pdh),
266 nav: getNavUrl(auth, file, pdh),
267 imageUrl: isFileImage(file.basename) ? getImageUrl(auth, file, pdh) : undefined,
270 case isArrayOfType(input, CWLType.DIRECTORY):
271 const directories = (input as DirectoryArrayCommandInputParameter).value || [];
272 return directories.map(directory => directoryToProcessIOValue(directory, auth, pdh));
279 const getKeepUrl = (file: File | Directory, pdh?: string): string => {
280 const isKeepUrl = file.location?.startsWith('keep:') || false;
281 const keepUrl = isKeepUrl ? file.location : pdh ? `keep:${pdh}/${file.location}` : file.location;
282 return keepUrl || '';
285 const getNavUrl = (auth: AuthState, file: File | Directory, pdh?: string): string => {
286 let keepUrl = getKeepUrl(file, pdh).replace('keep:', '');
287 return (getInlineFileUrl(`${auth.config.keepWebServiceUrl}/c=${keepUrl}?api_token=${auth.apiToken}`, auth.config.keepWebServiceUrl, auth.config.keepWebInlineServiceUrl));
290 const getImageUrl = (auth: AuthState, file: File, pdh?: string): string => {
291 const keepUrl = getKeepUrl(file, pdh).replace('keep:', '');
292 return getInlineFileUrl(`${auth.config.keepWebServiceUrl}/c=${keepUrl}?api_token=${auth.apiToken}`, auth.config.keepWebServiceUrl, auth.config.keepWebInlineServiceUrl);
295 const isFileImage = (basename?: string): boolean => {
296 return basename ? (mime.getType(basename) || "").startsWith('image/') : false;
299 const normalizeDirectoryLocation = (directory: Directory): Directory => ({
301 location: (directory.location || '').endsWith('/') ? directory.location : directory.location + '/',
304 const directoryToProcessIOValue = (directory: Directory, auth: AuthState, pdh?: string): ProcessIOValue => {
305 const normalizedDirectory = normalizeDirectoryLocation(directory);
307 display: getKeepUrl(normalizedDirectory, pdh),
308 nav: getNavUrl(auth, normalizedDirectory, pdh),