//
// SPDX-License-Identifier: AGPL-3.0
-import React, { ReactElement, memo, useState } from "react";
+import React, { ReactElement, memo } from "react";
import { Dispatch } from "redux";
import {
StyleRulesCallback,
CardContent,
Tooltip,
Typography,
- Tabs,
- Tab,
Table,
TableHead,
TableBody,
CircularProgress,
} from "@material-ui/core";
import { ArvadosTheme } from "common/custom-theme";
-import { CloseIcon, ImageIcon, InputIcon, ImageOffIcon, OutputIcon, MaximizeIcon, UnMaximizeIcon, InfoIcon } from "components/icon/icon";
+import { CloseIcon, InputIcon, OutputIcon, MaximizeIcon, UnMaximizeIcon, InfoIcon } from "components/icon/icon";
import { MPVPanelProps } from "components/multi-panel-view/multi-panel-view";
import {
BooleanCommandInputParameter,
import { Process } from "store/processes/process";
import { navigateTo } from "store/navigation/navigation-action";
import classNames from "classnames";
-import { DefaultCodeSnippet } from "components/default-code-snippet/default-code-snippet";
+import { DefaultVirtualCodeSnippet } from "components/default-code-snippet/default-virtual-code-snippet";
import { KEEP_URL_REGEX } from "models/resource";
import { FixedSizeList } from 'react-window';
import AutoSizer from "react-virtualized-auto-sizer";
+import { LinkProps } from "@material-ui/core/Link";
+import { ConditionalTabs } from "components/conditional-tabs/conditional-tabs";
type CssRules =
| "card"
| "iconHeader"
| "tableWrapper"
| "paramTableRoot"
+ | "paramTableCellText"
| "mountsTableRoot"
- | "rowStyles"
- | "valueWrapper"
- | "value"
+ | "jsonWrapper"
| "keepLink"
| "collectionLink"
| "secondaryVal"
| "emptyValue"
| "noBorderRow"
- | "symmetricTabs";
+ | "symmetricTabs"
+ | "wrapTooltip";
const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
card: {
},
// Card content
content: {
- height: `calc(100% - ${theme.spacing.unit * 7}px - ${theme.spacing.unit * 1.5}px)`,
+ height: `calc(100% - ${theme.spacing.unit * 6}px)`,
padding: theme.spacing.unit * 1.0,
paddingTop: 0,
"&:last-child": {
color: theme.customs.colors.greyD,
fontSize: "1.875rem",
},
- // Applies to each tab's content
+ // Applies to table tab and collection table content
tableWrapper: {
height: "auto",
- maxHeight: `calc(100% - ${theme.spacing.unit * 3}px)`,
+ maxHeight: `calc(100% - ${theme.spacing.unit * 6}px)`,
overflow: "auto",
// Use flexbox to keep scrolling at the virtual list level
display: "flex",
flexDirection: "column",
- alignItems: "start", // Prevents scroll bars at different levels in json tab
+ alignItems: "stretch", // Stretches output collection to full width
+
},
+
+ // Param table virtual list styles
paramTableRoot: {
display: "flex",
flexDirection: "column",
overflow: "hidden",
-
// Flex header
"& thead tr": {
alignItems: "end",
overflow: "hidden",
},
// Column width overrides
+ "& th:nth-of-type(1), & td:nth-of-type(1)": {
+ flexGrow: 0.7,
+ },
"& th:nth-last-of-type(1), & td:nth-last-of-type(1)": {
flexGrow: 2,
},
},
- // Flex body cells
- "& > tbody tr td": {
- padding: "4px 25px 4px",
- overflow: "auto hidden",
- display: "flex",
- flexDirection: "row",
- alignItems: "center",
- whiteSpace: "nowrap",
+ // Flex body rows
+ "& tbody tr": {
+ height: "40px",
+ // Flex body cells
+ "& td": {
+ padding: "2px 25px 2px",
+ overflow: "hidden",
+ display: "flex",
+ flexDirection: "row",
+ alignItems: "center",
+ whiteSpace: "nowrap",
+ },
+ },
+ },
+ // Param value cell typography styles
+ paramTableCellText: {
+ overflow: "hidden",
+ display: "flex",
+ // Every cell contents requires a wrapper for the ellipsis
+ // since adding ellipses to an anchor element parent results in misaligned tooltip
+ "& a, & span": {
+ overflow: "hidden",
+ textOverflow: "ellipsis",
+ },
+ '& pre': {
+ margin: 0,
+ overflow: "hidden",
+ textOverflow: "ellipsis",
},
},
mountsTableRoot: {
paddingRight: "25px",
},
},
- // Virtual list row styles
- rowStyles: {
- height: "40px",
- "& td": {
- paddingTop: "2px",
- paddingBottom: "2px",
- },
- },
- // Cell typography
- valueWrapper: {
- display: "flex",
- alignItems: "center",
- flexDirection: "row",
- height: "100%",
- overflow: "hidden",
- '& pre': {
- margin: 0,
- },
- },
- value: {
- display: "flex",
- gap: "10px",
- flexWrap: "wrap",
- maxWidth: "100%",
- maxHeight: "100%",
- whiteSpace: "nowrap",
- "& span": {
- display: "inline",
- },
- "& a, & pre": {
- overflow: "hidden",
- textOverflow: "ellipsis",
- },
+ // JSON tab wrapper
+ jsonWrapper: {
+ height: `calc(100% - ${theme.spacing.unit * 6}px)`,
},
keepLink: {
color: theme.palette.primary.main,
flexBasis: "0",
},
},
+ wrapTooltip: {
+ maxWidth: "600px",
+ wordWrap: "break-word",
+ },
});
export enum ProcessIOCardType {
forceShowParams?: boolean;
}
-export interface ProcessIOCardActionProps {
- navigateTo: (uuid: string) => void;
-}
-
-const mapDispatchToProps = (dispatch: Dispatch): ProcessIOCardActionProps => ({
- navigateTo: uuid => dispatch<any>(navigateTo(uuid)),
-});
-
-type ProcessIOCardProps = ProcessIOCardDataProps & ProcessIOCardActionProps & WithStyles<CssRules> & MPVPanelProps;
+type ProcessIOCardProps = ProcessIOCardDataProps & WithStyles<CssRules> & MPVPanelProps;
export const ProcessIOCard = withStyles(styles)(
- connect(
- null,
- mapDispatchToProps
- )(
- ({
- classes,
- label,
- params,
- raw,
- mounts,
- outputUuid,
- doHidePanel,
- doMaximizePanel,
- doUnMaximizePanel,
- panelMaximized,
- panelName,
- process,
- navigateTo,
- forceShowParams,
- }: ProcessIOCardProps) => {
- const [mainProcTabState, setMainProcTabState] = useState(0);
- const [subProcTabState, setSubProcTabState] = useState(0);
- const handleMainProcTabChange = (event: React.MouseEvent<HTMLElement>, value: number) => {
- setMainProcTabState(value);
- };
- const handleSubProcTabChange = (event: React.MouseEvent<HTMLElement>, value: number) => {
- setSubProcTabState(value);
- };
-
- const [showImagePreview, setShowImagePreview] = useState(false);
-
- const PanelIcon = label === ProcessIOCardType.INPUT ? InputIcon : OutputIcon;
- const mainProcess = !(process && process!.containerRequest.requestingContainerUuid);
- const showParamTable = mainProcess || forceShowParams;
-
- const loading = raw === null || raw === undefined || params === null;
-
- const hasRaw = !!(raw && Object.keys(raw).length > 0);
- const hasParams = !!(params && params.length > 0);
- // isRawLoaded allows subprocess panel to display raw even if it's {}
- const isRawLoaded = !!(raw && Object.keys(raw).length >= 0);
+ ({
+ classes,
+ label,
+ params,
+ raw,
+ mounts,
+ outputUuid,
+ doHidePanel,
+ doMaximizePanel,
+ doUnMaximizePanel,
+ panelMaximized,
+ panelName,
+ process,
+ forceShowParams,
+ }: ProcessIOCardProps) => {
+ const PanelIcon = label === ProcessIOCardType.INPUT ? InputIcon : OutputIcon;
+ const mainProcess = !(process && process!.containerRequest.requestingContainerUuid);
+ const showParamTable = mainProcess || forceShowParams;
+
+ const loading = raw === null || raw === undefined || params === null;
+
+ const hasRaw = !!(raw && Object.keys(raw).length > 0);
+ const hasParams = !!(params && params.length > 0);
+ // isRawLoaded allows subprocess panel to display raw even if it's {}
+ const isRawLoaded = !!(raw && Object.keys(raw).length >= 0);
+
+ // Subprocess
+ const hasInputMounts = !!(label === ProcessIOCardType.INPUT && mounts && mounts.length);
+ const hasOutputCollecton = !!(label === ProcessIOCardType.OUTPUT && outputUuid);
+ // Subprocess should not show loading if hasOutputCollection or hasInputMounts
+ const subProcessLoading = loading && !hasOutputCollecton && !hasInputMounts;
- // Subprocess
- const hasInputMounts = !!(label === ProcessIOCardType.INPUT && mounts && mounts.length);
- const hasOutputCollecton = !!(label === ProcessIOCardType.OUTPUT && outputUuid);
- // Subprocess should not show loading if hasOutputCollection or hasInputMounts
- const subProcessLoading = loading && !hasOutputCollecton && !hasInputMounts;
-
- return (
- <Card
- className={classes.card}
- data-cy="process-io-card"
- >
- <CardHeader
- className={classes.header}
- classes={{
- content: classes.title,
- avatar: classes.avatar,
- }}
- avatar={<PanelIcon className={classes.iconHeader} />}
- title={
- <Typography
- noWrap
- variant="h6"
- color="inherit"
- >
- {label}
- </Typography>
- }
- action={
- <div>
- {mainProcess && (
- <Tooltip
- title={"Toggle Image Preview"}
- disableFocusListener
- >
- <IconButton
- data-cy="io-preview-image-toggle"
- onClick={() => {
- setShowImagePreview(!showImagePreview);
- }}
- >
- {showImagePreview ? <ImageIcon /> : <ImageOffIcon />}
- </IconButton>
- </Tooltip>
- )}
- {doUnMaximizePanel && panelMaximized && (
- <Tooltip
- title={`Unmaximize ${panelName || "panel"}`}
- disableFocusListener
- >
- <IconButton onClick={doUnMaximizePanel}>
- <UnMaximizeIcon />
- </IconButton>
- </Tooltip>
- )}
- {doMaximizePanel && !panelMaximized && (
- <Tooltip
- title={`Maximize ${panelName || "panel"}`}
- disableFocusListener
- >
- <IconButton onClick={doMaximizePanel}>
- <MaximizeIcon />
- </IconButton>
- </Tooltip>
- )}
- {doHidePanel && (
- <Tooltip
- title={`Close ${panelName || "panel"}`}
- disableFocusListener
- >
- <IconButton
- disabled={panelMaximized}
- onClick={doHidePanel}
- >
- <CloseIcon />
- </IconButton>
- </Tooltip>
- )}
- </div>
- }
- />
- <CardContent className={classes.content}>
- {showParamTable ? (
- <>
- {/* raw is undefined until params are loaded */}
- {loading && (
- <Grid
- container
- item
- alignItems="center"
- justify="center"
+ return (
+ <Card
+ className={classes.card}
+ data-cy="process-io-card"
+ >
+ <CardHeader
+ className={classes.header}
+ classes={{
+ content: classes.title,
+ avatar: classes.avatar,
+ }}
+ avatar={<PanelIcon className={classes.iconHeader} />}
+ title={
+ <Typography
+ noWrap
+ variant="h6"
+ color="inherit"
+ >
+ {label}
+ </Typography>
+ }
+ action={
+ <div>
+ {doUnMaximizePanel && panelMaximized && (
+ <Tooltip
+ title={`Unmaximize ${panelName || "panel"}`}
+ disableFocusListener
+ >
+ <IconButton onClick={doUnMaximizePanel}>
+ <UnMaximizeIcon />
+ </IconButton>
+ </Tooltip>
+ )}
+ {doMaximizePanel && !panelMaximized && (
+ <Tooltip
+ title={`Maximize ${panelName || "panel"}`}
+ disableFocusListener
+ >
+ <IconButton onClick={doMaximizePanel}>
+ <MaximizeIcon />
+ </IconButton>
+ </Tooltip>
+ )}
+ {doHidePanel && (
+ <Tooltip
+ title={`Close ${panelName || "panel"}`}
+ disableFocusListener
+ >
+ <IconButton
+ disabled={panelMaximized}
+ onClick={doHidePanel}
>
- <CircularProgress />
- </Grid>
- )}
- {/* Once loaded, either raw or params may still be empty
- * Raw when all params are empty
- * Params when raw is provided by containerRequest properties but workflow mount is absent for preview
- */}
- {!loading && (hasRaw || hasParams) && (
- <>
- <Tabs
- value={mainProcTabState}
- onChange={handleMainProcTabChange}
- variant="fullWidth"
- className={classes.symmetricTabs}
- >
- {/* params will be empty on processes without workflow definitions in mounts, so we only show raw */}
- {hasParams && <Tab label="Parameters" />}
- {!forceShowParams && <Tab label="JSON" />}
- {hasOutputCollecton && <Tab label="Collection" />}
- </Tabs>
- {mainProcTabState === 0 && params && hasParams && (
- <div className={classes.tableWrapper}>
- <ProcessIOPreview
- data={params}
- showImagePreview={showImagePreview}
+ <CloseIcon />
+ </IconButton>
+ </Tooltip>
+ )}
+ </div>
+ }
+ />
+ <CardContent className={classes.content}>
+ {showParamTable ? (
+ <>
+ {/* raw is undefined until params are loaded */}
+ {loading && (
+ <Grid
+ container
+ item
+ alignItems="center"
+ justify="center"
+ >
+ <CircularProgress />
+ </Grid>
+ )}
+ {/* Once loaded, either raw or params may still be empty
+ * Raw when all params are empty
+ * Params when raw is provided by containerRequest properties but workflow mount is absent for preview
+ */}
+ {!loading && (hasRaw || hasParams) && (
+ <ConditionalTabs
+ variant="fullWidth"
+ className={classes.symmetricTabs}
+ tabs={[
+ {
+ // params will be empty on processes without workflow definitions in mounts, so we only show raw
+ show: hasParams,
+ label: "Parameters",
+ content: <ProcessIOPreview
+ data={params || []}
valueLabel={forceShowParams ? "Default value" : "Value"}
- />
- </div>
- )}
- {(mainProcTabState === 1 || !hasParams) && (
- <div className={classes.tableWrapper}>
- <ProcessIORaw data={raw} />
- </div>
- )}
- {mainProcTabState === 2 && hasOutputCollecton && (
- <>
- {outputUuid && (
- <Typography className={classes.collectionLink}>
- Output Collection:{" "}
- <MuiLink
- className={classes.keepLink}
- onClick={() => {
- navigateTo(outputUuid || "");
- }}
- >
- {outputUuid}
- </MuiLink>
- </Typography>
- )}
- <ProcessOutputCollectionFiles
- isWritable={false}
- currentItemUuid={outputUuid}
- />
- </>
- )}
-
- </>
- )}
- {!loading && !hasRaw && !hasParams && (
- <Grid
- container
- item
- alignItems="center"
- justify="center"
- >
- <DefaultView messages={["No parameters found"]} />
- </Grid>
- )}
- </>
- ) : (
- // Subprocess
- <>
- {subProcessLoading ? (
- <Grid
- container
- item
- alignItems="center"
- justify="center"
- >
- <CircularProgress />
- </Grid>
- ) : !subProcessLoading && (hasInputMounts || hasOutputCollecton || isRawLoaded) ? (
- <>
- <Tabs
- value={subProcTabState}
- onChange={handleSubProcTabChange}
- variant="fullWidth"
- className={classes.symmetricTabs}
- >
- {hasInputMounts && <Tab label="Collections" />}
- {hasOutputCollecton && <Tab label="Collection" />}
- {isRawLoaded && <Tab label="JSON" />}
- </Tabs>
- <div className={classes.tableWrapper}>
- {subProcTabState === 0 && hasInputMounts && <ProcessInputMounts mounts={mounts || []} />}
- {subProcTabState === 0 && hasOutputCollecton && (
- <>
- {outputUuid && (
- <Typography className={classes.collectionLink}>
- Output Collection:{" "}
- <MuiLink
- className={classes.keepLink}
- onClick={() => {
- navigateTo(outputUuid || "");
- }}
- >
- {outputUuid}
- </MuiLink>
- </Typography>
- )}
- <ProcessOutputCollectionFiles
- isWritable={false}
- currentItemUuid={outputUuid}
- />
- </>
- )}
- {isRawLoaded && (subProcTabState === 1 || (!hasInputMounts && !hasOutputCollecton)) && (
- <div className={classes.tableWrapper}>
- <ProcessIORaw data={raw} />
- </div>
- )}
- </div>
- </>
- ) : (
- <Grid
- container
- item
- alignItems="center"
- justify="center"
- >
- <DefaultView messages={["No data to display"]} />
- </Grid>
- )}
- </>
- )}
- </CardContent>
- </Card>
- );
- }
- )
+ />,
+ },
+ {
+ show: !forceShowParams,
+ label: "JSON",
+ content: <ProcessIORaw data={raw} />,
+ },
+ {
+ show: hasOutputCollecton,
+ label: "Collection",
+ content: <ProcessOutputCollection outputUuid={outputUuid} />,
+ },
+ ]}
+ />
+ )}
+ {!loading && !hasRaw && !hasParams && (
+ <Grid
+ container
+ item
+ alignItems="center"
+ justify="center"
+ >
+ <DefaultView messages={["No parameters found"]} />
+ </Grid>
+ )}
+ </>
+ ) : (
+ // Subprocess
+ <>
+ {subProcessLoading ? (
+ <Grid
+ container
+ item
+ alignItems="center"
+ justify="center"
+ >
+ <CircularProgress />
+ </Grid>
+ ) : !subProcessLoading && (hasInputMounts || hasOutputCollecton || isRawLoaded) ? (
+ <ConditionalTabs
+ variant="fullWidth"
+ className={classes.symmetricTabs}
+ tabs={[
+ {
+ show: hasInputMounts,
+ label: "Collections",
+ content: <ProcessInputMounts mounts={mounts || []} />,
+ },
+ {
+ show: hasOutputCollecton,
+ label: "Collection",
+ content: <ProcessOutputCollection outputUuid={outputUuid} />,
+ },
+ {
+ show: isRawLoaded,
+ label: "JSON",
+ content: <ProcessIORaw data={raw} />,
+ },
+ ]}
+ />
+ ) : (
+ <Grid
+ container
+ item
+ alignItems="center"
+ justify="center"
+ >
+ <DefaultView messages={["No data to display"]} />
+ </Grid>
+ )}
+ </>
+ )}
+ </CardContent>
+ </Card>
+ );
+ }
);
export type ProcessIOValue = {
interface ProcessIOPreviewDataProps {
data: ProcessIOParameter[];
- showImagePreview: boolean;
valueLabel: string;
+ hidden?: boolean;
}
type ProcessIOPreviewProps = ProcessIOPreviewDataProps & WithStyles<CssRules>;
const ProcessIOPreview = memo(
- withStyles(styles)(({ classes, data, showImagePreview, valueLabel }: ProcessIOPreviewProps) => {
+ withStyles(styles)(({ data, valueLabel, hidden, classes }: ProcessIOPreviewProps) => {
const showLabel = data.some((param: ProcessIOParameter) => param.label);
const hasMoreValues = (index: number) => (
- data[index+1] && !(data[index+1].id || data[index+1].label)
+ data[index+1] && !isMainRow(data[index+1])
+ );
+
+ const isMainRow = (param: ProcessIOParameter) => (
+ param &&
+ ((param.id || param.label) &&
+ !param.value.secondary)
);
const RenderRow = ({index, style}) => {
const rowClasses = {
[classes.noBorderRow]: hasMoreValues(index),
- [classes.rowStyles]: true,
};
- return <TableRow style={style} className={classNames(rowClasses)}>
- <TableCell>{param.id}</TableCell>
- {showLabel && <TableCell>{param.label}</TableCell>}
+ return <TableRow
+ style={style}
+ className={classNames(rowClasses)}
+ data-cy={isMainRow(param) ? "process-io-param" : ""}>
+ <TableCell>
+ <Tooltip title={param.id}>
+ <Typography className={classes.paramTableCellText}>
+ <span>
+ {param.id}
+ </span>
+ </Typography>
+ </Tooltip>
+ </TableCell>
+ {showLabel && <TableCell>
+ <Tooltip title={param.label}>
+ <Typography className={classes.paramTableCellText}>
+ <span>
+ {param.label}
+ </span>
+ </Typography>
+ </Tooltip>
+ </TableCell>}
<TableCell>
<ProcessValuePreview
value={param.value}
- showImagePreview={showImagePreview}
/>
</TableCell>
<TableCell>
- <Typography className={classes.valueWrapper}>
- <span className={classes.value}>
- {param.value.collection}
- </span>
+ <Typography className={classes.paramTableCellText}>
+ {/** Collection is an anchor so doesn't require wrapper element */}
+ {param.value.collection}
</Typography>
</TableCell>
</TableRow>;
};
- return (
+ return <div className={classes.tableWrapper} hidden={hidden}>
<Table
className={classes.paramTableRoot}
aria-label="Process IO Preview"
</AutoSizer>
</TableBody>
</Table>
- );
+ </div>;
})
);
interface ProcessValuePreviewProps {
value: ProcessIOValue;
- showImagePreview: boolean;
}
-const ProcessValuePreview = withStyles(styles)(({ value, showImagePreview, classes }: ProcessValuePreviewProps & WithStyles<CssRules>) => (
- <Typography className={classes.valueWrapper}>
- <span className={classNames(classes.value, value.secondary && classes.secondaryVal)}>{value.display}</span>
+const ProcessValuePreview = withStyles(styles)(({ value, classes }: ProcessValuePreviewProps & WithStyles<CssRules>) => (
+ <Typography className={classNames(classes.paramTableCellText, value.secondary && classes.secondaryVal)}>
+ {value.display}
</Typography>
));
interface ProcessIORawDataProps {
data: ProcessIOParameter[];
+ hidden?: boolean;
}
-const ProcessIORaw = withStyles(styles)(({ data }: ProcessIORawDataProps) => (
- <Paper elevation={0}>
- <DefaultCodeSnippet
- lines={[JSON.stringify(data, null, 2)]}
- linked
- />
- </Paper>
+const ProcessIORaw = withStyles(styles)(({ data, hidden, classes }: ProcessIORawDataProps & WithStyles<CssRules>) => (
+ <div className={classes.jsonWrapper} hidden={hidden}>
+ <Paper elevation={0} style={{minWidth: "100%", height: "100%"}}>
+ <DefaultVirtualCodeSnippet
+ lines={JSON.stringify(data, null, 2).split('\n')}
+ linked
+ />
+ </Paper>
+ </div>
));
interface ProcessInputMountsDataProps {
mounts: InputCollectionMount[];
+ hidden?: boolean;
}
type ProcessInputMountsProps = ProcessInputMountsDataProps & WithStyles<CssRules>;
const ProcessInputMounts = withStyles(styles)(
connect((state: RootState) => ({
auth: state.auth,
- }))(({ mounts, classes, auth }: ProcessInputMountsProps & { auth: AuthState }) => (
+ }))(({ mounts, hidden, classes, auth }: ProcessInputMountsProps & { auth: AuthState }) => (
<Table
className={classes.mountsTableRoot}
aria-label="Process Input Mounts"
+ hidden={hidden}
>
<TableHead>
<TableRow>
))
);
+export interface ProcessOutputCollectionActionProps {
+ navigateTo: (uuid: string) => void;
+}
+
+const mapNavigateToProps = (dispatch: Dispatch): ProcessOutputCollectionActionProps => ({
+ navigateTo: uuid => dispatch<any>(navigateTo(uuid)),
+});
+
+type ProcessOutputCollectionProps = {outputUuid: string | undefined, hidden?: boolean} & ProcessOutputCollectionActionProps & WithStyles<CssRules>;
+
+const ProcessOutputCollection = withStyles(styles)(connect(null, mapNavigateToProps)(({ outputUuid, hidden, navigateTo, classes }: ProcessOutputCollectionProps) => (
+ <div className={classes.tableWrapper} hidden={hidden}>
+ <>
+ {outputUuid && (
+ <Typography className={classes.collectionLink}>
+ Output Collection:{" "}
+ <MuiLink
+ className={classes.keepLink}
+ onClick={() => {
+ navigateTo(outputUuid || "");
+ }}
+ >
+ {outputUuid}
+ </MuiLink>
+ </Typography>
+ )}
+ <ProcessOutputCollectionFiles
+ isWritable={false}
+ currentItemUuid={outputUuid}
+ />
+ </>
+ </div>
+)));
+
type FileWithSecondaryFiles = {
secondaryFiles: File[];
};
case isPrimitiveOfType(input, CWLType.BOOLEAN):
const boolValue = (input as BooleanCommandInputParameter).value;
return boolValue !== undefined && !(Array.isArray(boolValue) && boolValue.length === 0)
- ? [{ display: renderPrimitiveValue(boolValue, false) }]
+ ? [{ display: <PrimitiveTooltip data={boolValue}>{renderPrimitiveValue(boolValue, false)}</PrimitiveTooltip> }]
: [{ display: <EmptyValue /> }];
case isPrimitiveOfType(input, CWLType.INT):
return intValue !== undefined &&
// Missing values are empty array
!(Array.isArray(intValue) && intValue.length === 0)
- ? [{ display: renderPrimitiveValue(intValue, false) }]
+ ? [{ display: <PrimitiveTooltip data={intValue}>{renderPrimitiveValue(intValue, false)}</PrimitiveTooltip> }]
: [{ display: <EmptyValue /> }];
case isPrimitiveOfType(input, CWLType.FLOAT):
case isPrimitiveOfType(input, CWLType.DOUBLE):
const floatValue = (input as FloatCommandInputParameter).value;
return floatValue !== undefined && !(Array.isArray(floatValue) && floatValue.length === 0)
- ? [{ display: renderPrimitiveValue(floatValue, false) }]
+ ? [{ display: <PrimitiveTooltip data={floatValue}>{renderPrimitiveValue(floatValue, false)}</PrimitiveTooltip> }]
: [{ display: <EmptyValue /> }];
case isPrimitiveOfType(input, CWLType.STRING):
const stringValue = (input as StringCommandInputParameter).value || undefined;
return stringValue !== undefined && !(Array.isArray(stringValue) && stringValue.length === 0)
- ? [{ display: renderPrimitiveValue(stringValue, false) }]
+ ? [{ display: <PrimitiveTooltip data={stringValue}>{renderPrimitiveValue(stringValue, false)}</PrimitiveTooltip> }]
: [{ display: <EmptyValue /> }];
case isPrimitiveOfType(input, CWLType.FILE):
case getEnumType(input) !== null:
const enumValue = (input as EnumCommandInputParameter).value;
- return enumValue !== undefined && enumValue ? [{ display: <pre>{enumValue}</pre> }] : [{ display: <EmptyValue /> }];
+ return enumValue !== undefined && enumValue ? [{ display: <PrimitiveTooltip data={enumValue}>{enumValue}</PrimitiveTooltip> }] : [{ display: <EmptyValue /> }];
case isArrayOfType(input, CWLType.STRING):
const strArray = (input as StringArrayCommandInputParameter).value || [];
- return strArray.length ? [{ display: <>{strArray.map(val => renderPrimitiveValue(val, true))}</> }] : [{ display: <EmptyValue /> }];
+ return strArray.length ? [{ display: <PrimitiveArrayTooltip data={strArray}>{strArray.map(val => renderPrimitiveValue(val, true))}</PrimitiveArrayTooltip> }] : [{ display: <EmptyValue /> }];
case isArrayOfType(input, CWLType.INT):
case isArrayOfType(input, CWLType.LONG):
const intArray = (input as IntArrayCommandInputParameter).value || [];
- return intArray.length ? [{ display: <>{intArray.map(val => renderPrimitiveValue(val, true))}</> }] : [{ display: <EmptyValue /> }];
+ return intArray.length ? [{ display: <PrimitiveArrayTooltip data={intArray}>{intArray.map(val => renderPrimitiveValue(val, true))}</PrimitiveArrayTooltip> }] : [{ display: <EmptyValue /> }];
case isArrayOfType(input, CWLType.FLOAT):
case isArrayOfType(input, CWLType.DOUBLE):
const floatArray = (input as FloatArrayCommandInputParameter).value || [];
- return floatArray.length ? [{ display: <>{floatArray.map(val => renderPrimitiveValue(val, true))}</> }] : [{ display: <EmptyValue /> }];
+ return floatArray.length ? [{ display: <PrimitiveArrayTooltip data={floatArray}>{floatArray.map(val => renderPrimitiveValue(val, true))}</PrimitiveArrayTooltip> }] : [{ display: <EmptyValue /> }];
case isArrayOfType(input, CWLType.FILE):
const fileArrayMainFiles = (input as FileArrayCommandInputParameter).value || [];
}
};
+interface PrimitiveTooltipProps {
+ data: boolean | number | string;
+}
+
+const PrimitiveTooltip = (props: React.PropsWithChildren<PrimitiveTooltipProps>) => (
+ <Tooltip title={typeof props.data !== 'object' ? String(props.data) : ""}>
+ <pre>{props.children}</pre>
+ </Tooltip>
+);
+
+interface PrimitiveArrayTooltipProps {
+ data: string[];
+}
+
+const PrimitiveArrayTooltip = (props: React.PropsWithChildren<PrimitiveArrayTooltipProps>) => (
+ <Tooltip title={props.data.join(', ')}>
+ <span>{props.children}</span>
+ </Tooltip>
+);
+
+
const renderPrimitiveValue = (value: any, asChip: boolean) => {
const isObject = typeof value === "object";
if (!isObject) {
<Chip
key={value}
label={String(value)}
+ style={{marginRight: "10px"}}
/>
) : (
- <pre key={value}>{String(value)}</pre>
+ <>{String(value)}</>
);
} else {
return asChip ? <UnsupportedValueChip /> : <UnsupportedValue />;
// Passing a pdh always returns a relative wb2 collection url
const pdhWbPath = getNavUrl(pdhUrl, auth);
return pdhUrl && pdhWbPath ? (
- <Tooltip title={"View collection in Workbench"}>
+ <Tooltip title={<>View collection in Workbench<br />{pdhUrl}</>}>
<RouterLink
to={pdhWbPath}
className={classes.keepLink}
const keepUrlPathNav = getKeepNavUrl(auth, res, pdh);
return keepUrlPathNav ? (
- <Tooltip title={"View in keep-web"}>
+ <Tooltip classes={{tooltip: classes.wrapTooltip}} title={<>View in keep-web<br />{keepUrlPath || "/"}</>}>
<a
className={classes.keepLink}
href={keepUrlPathNav}
target="_blank"
- rel="noopener"
+ rel="noopener noreferrer"
>
{keepUrlPath || "/"}
</a>
};
};
+type MuiLinkWithTooltipProps = WithStyles<CssRules> & React.PropsWithChildren<LinkProps>;
+
+const MuiLinkWithTooltip = withStyles(styles)((props: MuiLinkWithTooltipProps) => (
+ <Tooltip title={props.title} classes={{tooltip: props.classes.wrapTooltip}}>
+ <MuiLink {...props}>
+ {props.children}
+ </MuiLink>
+ </Tooltip>
+));
+
const fileToProcessIOValue = (file: File, secondary: boolean, auth: AuthState, pdh: string | undefined, mainFilePdh: string): ProcessIOValue => {
if (isExternalValue(file)) {
return { display: <UnsupportedValue /> };
if (isFileUrl(file.location)) {
return {
display: (
- <MuiLink
+ <MuiLinkWithTooltip
href={file.location}
target="_blank"
rel="noopener"
+ title={file.location}
>
{file.location}
- </MuiLink>
+ </MuiLinkWithTooltip>
),
secondary,
};