1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
5 import React, { ReactElement, memo } from "react";
6 import { Dispatch } from "redux";
7 import { CustomStyleRulesCallback } from 'common/custom-theme';
24 } from "@mui/material";
25 import { WithStyles } from '@mui/styles';
26 import withStyles from '@mui/styles/withStyles';
27 import { ArvadosTheme } from "common/custom-theme";
28 import { CloseIcon, InputIcon, OutputIcon, MaximizeIcon, UnMaximizeIcon, InfoIcon } from "components/icon/icon";
29 import { MPVPanelProps } from "components/multi-panel-view/multi-panel-view";
31 BooleanCommandInputParameter,
32 CommandInputParameter,
35 DirectoryArrayCommandInputParameter,
36 DirectoryCommandInputParameter,
37 EnumCommandInputParameter,
38 FileArrayCommandInputParameter,
39 FileCommandInputParameter,
40 FloatArrayCommandInputParameter,
41 FloatCommandInputParameter,
42 IntArrayCommandInputParameter,
43 IntCommandInputParameter,
47 StringArrayCommandInputParameter,
48 StringCommandInputParameter,
50 } from "models/workflow";
51 import { CommandOutputParameter } from "cwlts/mappings/v1.0/CommandOutputParameter";
52 import { File } from "models/workflow";
53 import { getInlineFileUrl } from "views-components/context-menu/actions/helpers";
54 import { AuthState } from "store/auth/auth-reducer";
55 import mime from "mime";
56 import { DefaultView } from "components/default-view/default-view";
57 import { getNavUrl } from "routes/routes";
58 import { Link as RouterLink } from "react-router-dom";
59 import { Link as MuiLink } from "@mui/material";
60 import { InputCollectionMount } from "store/processes/processes-actions";
61 import { connect } from "react-redux";
62 import { RootState } from "store/store";
63 import { ProcessOutputCollectionFiles } from "./process-output-collection-files";
64 import { Process } from "store/processes/process";
65 import { navigateTo } from "store/navigation/navigation-action";
66 import classNames from "classnames";
67 import { DefaultVirtualCodeSnippet } from "components/default-code-snippet/default-virtual-code-snippet";
68 import { KEEP_URL_REGEX } from "models/resource";
69 import { FixedSizeList } from 'react-window';
70 import AutoSizer from "react-virtualized-auto-sizer";
71 import { LinkProps } from "@mui/material/Link";
72 import { ConditionalTabs } from "components/conditional-tabs/conditional-tabs";
82 | "virtualListTableRoot"
83 | "parameterTableRoot"
84 | "inputMountTableRoot"
85 | "virtualListCellText"
95 const styles: CustomStyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
100 paddingTop: theme.spacing(1),
104 fontSize: "1.875rem",
105 color: theme.customs.colors.greyL,
108 alignSelf: "flex-start",
109 paddingTop: theme.spacing(0.5),
113 height: `calc(100% - ${theme.spacing(6)})`,
114 padding: theme.spacing(1),
117 paddingBottom: theme.spacing(1),
123 paddingTop: theme.spacing(0.5),
124 color: theme.customs.colors.greyD,
125 fontSize: "1.875rem",
127 // Applies to parameters / input collection virtual lists and output collection DE
130 maxHeight: `calc(100% - ${theme.spacing(6)})`,
132 // Use flexbox to keep scrolling at the virtual list level
134 flexDirection: "column",
135 alignItems: "stretch", // Stretches output collection to full width
138 // Parameters / input collection virtual list table styles
139 virtualListTableRoot: {
141 flexDirection: "column",
147 padding: "4px 25px 10px",
151 height: "100vh", // Must be constrained by panel maxHeight
153 // Flex header/body rows
154 "& thead tr, & > tbody tr": {
156 // Flex header/body cells
169 padding: "2px 25px 2px",
172 flexDirection: "row",
173 alignItems: "center",
174 whiteSpace: "nowrap",
178 // Parameter tab table column widths
179 parameterTableRoot: {
180 "& thead tr, & > tbody tr": {
182 "& th:nth-of-type(1), & td:nth-of-type(1)": {
186 "& th:nth-last-of-type(1), & td:nth-last-of-type(1)": {
191 // Input collection tab table column widths
192 inputMountTableRoot: {
193 "& thead tr, & > tbody tr": {
195 "& th:nth-of-type(1), & td:nth-of-type(1)": {
199 "& th:nth-last-of-type(1), & td:nth-last-of-type(1)": {
204 // Param value cell typography styles
205 virtualListCellText: {
208 // Every cell contents requires a wrapper for the ellipsis
209 // since adding ellipses to an anchor element parent results in misaligned tooltip
212 textOverflow: "ellipsis",
217 textOverflow: "ellipsis",
222 height: `calc(100% - ${theme.spacing(6)})`,
225 color: theme.palette.primary.main,
226 textDecoration: "none",
227 // Overflow wrap for mounts table
228 overflowWrap: "break-word",
231 // Output collection tab link
235 color: theme.palette.primary.main,
236 textDecoration: "none",
237 overflowWrap: "break-word",
245 color: theme.customs.colors.grey700,
249 borderBottom: "none",
251 paddingBottom: "2px",
262 wordWrap: "break-word",
266 export enum ProcessIOCardType {
267 INPUT = "Input Parameters",
268 OUTPUT = "Output Parameters",
270 export interface ProcessIOCardDataProps {
272 label: ProcessIOCardType;
273 params: ProcessIOParameter[] | null;
275 mounts?: InputCollectionMount[];
277 forceShowParams?: boolean;
280 type ProcessIOCardProps = ProcessIOCardDataProps & WithStyles<CssRules> & MPVPanelProps;
282 export const ProcessIOCard = withStyles(styles)(
297 }: ProcessIOCardProps) => {
298 const PanelIcon = label === ProcessIOCardType.INPUT ? InputIcon : OutputIcon;
299 const mainProcess = !(process && process!.containerRequest.requestingContainerUuid);
300 const showParamTable = mainProcess || forceShowParams;
302 const loading = raw === null || raw === undefined || params === null;
304 const hasRaw = !!(raw && Object.keys(raw).length > 0);
305 const hasParams = !!(params && params.length > 0);
306 // isRawLoaded allows subprocess panel to display raw even if it's {}
307 const isRawLoaded = !!(raw && Object.keys(raw).length >= 0);
310 const hasInputMounts = !!(label === ProcessIOCardType.INPUT && mounts && mounts.length);
311 const hasOutputCollecton = !!(label === ProcessIOCardType.OUTPUT && outputUuid);
312 // Subprocess should not show loading if hasOutputCollection or hasInputMounts
313 const subProcessLoading = loading && !hasOutputCollecton && !hasInputMounts;
317 className={classes.card}
318 data-cy="process-io-card"
321 className={classes.header}
323 content: classes.title,
324 avatar: classes.avatar,
326 avatar={<PanelIcon className={classes.iconHeader} />}
338 {doUnMaximizePanel && panelMaximized && (
340 title={`Unmaximize ${panelName || "panel"}`}
343 <IconButton onClick={doUnMaximizePanel} size="large">
348 {doMaximizePanel && !panelMaximized && (
350 title={`Maximize ${panelName || "panel"}`}
353 <IconButton onClick={doMaximizePanel} size="large">
360 title={`Close ${panelName || "panel"}`}
363 <IconButton disabled={panelMaximized} onClick={doHidePanel} size="large">
371 <CardContent className={classes.content}>
374 {/* raw is undefined until params are loaded */}
380 justifyContent="center"
382 <CircularProgress data-cy="process-io-circular-progress" />
385 {/* Once loaded, either raw or params may still be empty
386 * Raw when all params are empty
387 * Params when raw is provided by containerRequest properties but workflow mount is absent for preview
389 {!loading && (hasRaw || hasParams) && (
392 className={classes.symmetricTabs}
395 // params will be empty on processes without workflow definitions in mounts, so we only show raw
398 content: <ProcessIOPreview
400 valueLabel={forceShowParams ? "Default value" : "Value"}
404 show: !forceShowParams,
406 content: <ProcessIORaw data={raw} />,
409 show: hasOutputCollecton,
411 content: <ProcessOutputCollection outputUuid={outputUuid} />,
416 {!loading && !hasRaw && !hasParams && (
421 justifyContent="center"
423 <DefaultView messages={["No parameters found"]} />
430 {subProcessLoading ? (
435 justifyContent="center"
437 <CircularProgress data-cy="subprocess-circular-progress"/>
439 ) : !subProcessLoading && (hasInputMounts || hasOutputCollecton || isRawLoaded) ? (
442 className={classes.symmetricTabs}
445 show: hasInputMounts,
446 label: "Collections",
447 content: <ProcessInputMounts mounts={mounts || []} />,
450 show: hasOutputCollecton,
452 content: <ProcessOutputCollection outputUuid={outputUuid} />,
457 content: <ProcessIORaw data={raw} />,
466 justifyContent="center"
468 <DefaultView messages={["No data to display"]} />
479 export type ProcessIOValue = {
480 display: ReactElement<any, any>;
482 collection?: ReactElement<any, any>;
486 export type ProcessIOParameter = {
489 value: ProcessIOValue;
492 interface ProcessIOPreviewDataProps {
493 data: ProcessIOParameter[];
498 type ProcessIOPreviewProps = ProcessIOPreviewDataProps & WithStyles<CssRules>;
500 const ProcessIOPreview = memo(
501 withStyles(styles)(({ data, valueLabel, hidden, classes }: ProcessIOPreviewProps) => {
502 const showLabel = data.some((param: ProcessIOParameter) => param.label);
504 const hasMoreValues = (index: number) => (
505 data[index+1] && !isMainRow(data[index+1])
508 const isMainRow = (param: ProcessIOParameter) => (
510 ((param.id || param.label) &&
511 !param.value.secondary)
514 const RenderRow = ({index, style}) => {
515 const param = data[index];
518 [classes.noBorderRow]: hasMoreValues(index),
523 className={classNames(rowClasses)}
524 data-cy={isMainRow(param) ? "process-io-param" : ""}>
526 <Tooltip title={param.id}>
527 <Typography className={classes.virtualListCellText}>
534 {showLabel && <TableCell>
535 <Tooltip title={param.label}>
536 <Typography className={classes.virtualListCellText}>
549 <Typography className={classes.virtualListCellText}>
550 {/** Collection is an anchor so doesn't require wrapper element */}
551 {param.value.collection}
557 return <div className={classes.tableWrapper} hidden={hidden}>
559 className={classNames(classes.virtualListTableRoot, classes.parameterTableRoot)}
560 aria-label="Process IO Preview"
564 <TableCell>Name</TableCell>
565 {showLabel && <TableCell>Label</TableCell>}
566 <TableCell>{valueLabel}</TableCell>
567 <TableCell>Collection</TableCell>
572 {({ height, width }) =>
575 itemCount={data.length}
589 interface ProcessValuePreviewProps {
590 value: ProcessIOValue;
593 const ProcessValuePreview = withStyles(styles)(({ value, classes }: ProcessValuePreviewProps & WithStyles<CssRules>) => (
594 <Typography className={classNames(classes.virtualListCellText, value.secondary && classes.secondaryVal)}>
599 interface ProcessIORawDataProps {
600 data: ProcessIOParameter[];
604 const ProcessIORaw = withStyles(styles)(({ data, hidden, classes }: ProcessIORawDataProps & WithStyles<CssRules>) => (
605 <div className={classes.jsonWrapper} hidden={hidden}>
606 <Paper elevation={0} style={{minWidth: "100%", height: "100%"}}>
607 <DefaultVirtualCodeSnippet
608 lines={JSON.stringify(data, null, 2).split('\n')}
616 interface ProcessInputMountsDataProps {
617 mounts: InputCollectionMount[];
621 type ProcessInputMountsProps = ProcessInputMountsDataProps & WithStyles<CssRules>;
623 const ProcessInputMounts = withStyles(styles)(
624 connect((state: RootState) => ({
626 }))(({ mounts, hidden, classes, auth }: ProcessInputMountsProps & { auth: AuthState }) => {
628 const RenderRow = ({index, style}) => {
629 const mount = mounts[index];
635 <Tooltip title={mount.path}>
636 <Typography className={classes.virtualListCellText}>
637 <pre>{mount.path}</pre>
642 <Tooltip title={mount.pdh}>
643 <Typography className={classes.virtualListCellText}>
645 to={getNavUrl(mount.pdh, auth)}
646 className={classes.keepLink}
656 return <div className={classes.tableWrapper} hidden={hidden}>
658 className={classNames(classes.virtualListTableRoot, classes.inputMountTableRoot)}
659 aria-label="Process Input Mounts"
664 <TableCell>Path</TableCell>
665 <TableCell>Portable Data Hash</TableCell>
670 {({ height, width }) =>
673 itemCount={mounts.length}
687 export interface ProcessOutputCollectionActionProps {
688 navigateTo: (uuid: string) => void;
691 const mapNavigateToProps = (dispatch: Dispatch): ProcessOutputCollectionActionProps => ({
692 navigateTo: uuid => dispatch<any>(navigateTo(uuid)),
695 type ProcessOutputCollectionProps = {outputUuid: string | undefined, hidden?: boolean} & ProcessOutputCollectionActionProps & WithStyles<CssRules>;
697 const ProcessOutputCollection = withStyles(styles)(connect(null, mapNavigateToProps)(({ outputUuid, hidden, navigateTo, classes }: ProcessOutputCollectionProps) => (
698 <div className={classes.tableWrapper} hidden={hidden}>
701 <Typography className={classes.collectionLink} data-cy="output-uuid-display">
702 Output Collection:{" "}
704 className={classes.keepLink}
706 navigateTo(outputUuid || "");
713 <ProcessOutputCollectionFiles
715 currentItemUuid={outputUuid}
721 type FileWithSecondaryFiles = {
722 secondaryFiles: File[];
725 export const getIOParamDisplayValue = (auth: AuthState, input: CommandInputParameter | CommandOutputParameter, pdh?: string): ProcessIOValue[] => {
727 case isSecret(input):
728 return [{ display: <SecretValue /> }];
730 case isPrimitiveOfType(input, CWLType.BOOLEAN):
731 const boolValue = (input as BooleanCommandInputParameter).value;
732 return boolValue !== undefined && !(Array.isArray(boolValue) && boolValue.length === 0)
733 ? [{ display: <PrimitiveTooltip data={boolValue}>{renderPrimitiveValue(boolValue, false)}</PrimitiveTooltip> }]
734 : [{ display: <EmptyValue /> }];
736 case isPrimitiveOfType(input, CWLType.INT):
737 case isPrimitiveOfType(input, CWLType.LONG):
738 const intValue = (input as IntCommandInputParameter).value;
739 return intValue !== undefined &&
740 // Missing values are empty array
741 !(Array.isArray(intValue) && intValue.length === 0)
742 ? [{ display: <PrimitiveTooltip data={intValue}>{renderPrimitiveValue(intValue, false)}</PrimitiveTooltip> }]
743 : [{ display: <EmptyValue /> }];
745 case isPrimitiveOfType(input, CWLType.FLOAT):
746 case isPrimitiveOfType(input, CWLType.DOUBLE):
747 const floatValue = (input as FloatCommandInputParameter).value;
748 return floatValue !== undefined && !(Array.isArray(floatValue) && floatValue.length === 0)
749 ? [{ display: <PrimitiveTooltip data={floatValue}>{renderPrimitiveValue(floatValue, false)}</PrimitiveTooltip> }]
750 : [{ display: <EmptyValue /> }];
752 case isPrimitiveOfType(input, CWLType.STRING):
753 const stringValue = (input as StringCommandInputParameter).value || undefined;
754 return stringValue !== undefined && !(Array.isArray(stringValue) && stringValue.length === 0)
755 ? [{ display: <PrimitiveTooltip data={stringValue}>{renderPrimitiveValue(stringValue, false)}</PrimitiveTooltip> }]
756 : [{ display: <EmptyValue /> }];
758 case isPrimitiveOfType(input, CWLType.FILE):
759 const mainFile = (input as FileCommandInputParameter).value;
760 // secondaryFiles: File[] is not part of CommandOutputParameter so we cast to access secondaryFiles
761 const secondaryFiles = (mainFile as unknown as FileWithSecondaryFiles)?.secondaryFiles || [];
762 const files = [...(mainFile && !(Array.isArray(mainFile) && mainFile.length === 0) ? [mainFile] : []), ...secondaryFiles];
763 const mainFilePdhUrl = mainFile ? getResourcePdhUrl(mainFile, pdh) : "";
765 ? files.map((file, i) => fileToProcessIOValue(file, i > 0, auth, pdh, i > 0 ? mainFilePdhUrl : ""))
766 : [{ display: <EmptyValue /> }];
768 case isPrimitiveOfType(input, CWLType.DIRECTORY):
769 const directory = (input as DirectoryCommandInputParameter).value;
770 return directory !== undefined && !(Array.isArray(directory) && directory.length === 0)
771 ? [directoryToProcessIOValue(directory, auth, pdh)]
772 : [{ display: <EmptyValue /> }];
774 case getEnumType(input) !== null:
775 const enumValue = (input as EnumCommandInputParameter).value;
776 return enumValue !== undefined && enumValue ? [{ display: <PrimitiveTooltip data={enumValue}>{enumValue}</PrimitiveTooltip> }] : [{ display: <EmptyValue /> }];
778 case isArrayOfType(input, CWLType.STRING):
779 const strArray = (input as StringArrayCommandInputParameter).value || [];
780 return strArray.length ? [{ display: <PrimitiveArrayTooltip data={strArray}>{strArray.map(val => renderPrimitiveValue(val, true))}</PrimitiveArrayTooltip> }] : [{ display: <EmptyValue /> }];
782 case isArrayOfType(input, CWLType.INT):
783 case isArrayOfType(input, CWLType.LONG):
784 const intArray = (input as IntArrayCommandInputParameter).value || [];
785 return intArray.length ? [{ display: <PrimitiveArrayTooltip data={intArray}>{intArray.map(val => renderPrimitiveValue(val, true))}</PrimitiveArrayTooltip> }] : [{ display: <EmptyValue /> }];
787 case isArrayOfType(input, CWLType.FLOAT):
788 case isArrayOfType(input, CWLType.DOUBLE):
789 const floatArray = (input as FloatArrayCommandInputParameter).value || [];
790 return floatArray.length ? [{ display: <PrimitiveArrayTooltip data={floatArray}>{floatArray.map(val => renderPrimitiveValue(val, true))}</PrimitiveArrayTooltip> }] : [{ display: <EmptyValue /> }];
792 case isArrayOfType(input, CWLType.FILE):
793 const fileArrayMainFiles = (input as FileArrayCommandInputParameter).value || [];
794 const firstMainFilePdh = fileArrayMainFiles.length > 0 && fileArrayMainFiles[0] ? getResourcePdhUrl(fileArrayMainFiles[0], pdh) : "";
796 // Convert each main and secondaryFiles into array of ProcessIOValue preserving ordering
797 let fileArrayValues: ProcessIOValue[] = [];
798 for (let i = 0; i < fileArrayMainFiles.length; i++) {
799 const secondaryFiles = (fileArrayMainFiles[i] as unknown as FileWithSecondaryFiles)?.secondaryFiles || [];
800 fileArrayValues.push(
801 // Pass firstMainFilePdh to secondary files and every main file besides the first to hide pdh if equal
802 ...(fileArrayMainFiles[i] ? [fileToProcessIOValue(fileArrayMainFiles[i], false, auth, pdh, i > 0 ? firstMainFilePdh : "")] : []),
803 ...secondaryFiles.map(file => fileToProcessIOValue(file, true, auth, pdh, firstMainFilePdh))
807 return fileArrayValues.length ? fileArrayValues : [{ display: <EmptyValue /> }];
809 case isArrayOfType(input, CWLType.DIRECTORY):
810 const directories = (input as DirectoryArrayCommandInputParameter).value || [];
811 return directories.length ? directories.map(directory => directoryToProcessIOValue(directory, auth, pdh)) : [{ display: <EmptyValue /> }];
814 return [{ display: <UnsupportedValue /> }];
818 interface PrimitiveTooltipProps {
819 data: boolean | number | string;
822 const PrimitiveTooltip = (props: React.PropsWithChildren<PrimitiveTooltipProps>) => (
823 <Tooltip title={typeof props.data !== 'object' ? String(props.data) : ""}>
824 <pre>{props.children}</pre>
828 interface PrimitiveArrayTooltipProps {
832 const PrimitiveArrayTooltip = (props: React.PropsWithChildren<PrimitiveArrayTooltipProps>) => (
833 <Tooltip title={props.data.join(', ')}>
834 <span>{props.children}</span>
839 const renderPrimitiveValue = (value: any, asChip: boolean) => {
840 const isObject = typeof value === "object";
845 label={String(value)}
846 style={{marginRight: "10px"}}
852 return asChip ? <UnsupportedValueChip /> : <UnsupportedValue />;
857 * @returns keep url without keep: prefix
859 const getKeepUrl = (file: File | Directory, pdh?: string): string => {
860 const isKeepUrl = file.location?.startsWith("keep:") || false;
861 const keepUrl = isKeepUrl ? file.location?.replace("keep:", "") : pdh ? `${pdh}/${file.location}` : file.location;
862 return keepUrl || "";
865 interface KeepUrlProps {
867 res: File | Directory;
871 const getResourcePdhUrl = (res: File | Directory, pdh?: string): string => {
872 const keepUrl = getKeepUrl(res, pdh);
873 return keepUrl ? keepUrl.split("/").slice(0, 1)[0] : "";
876 const KeepUrlBase = withStyles(styles)(({ auth, res, pdh, classes }: KeepUrlProps & WithStyles<CssRules>) => {
877 const pdhUrl = getResourcePdhUrl(res, pdh);
878 // Passing a pdh always returns a relative wb2 collection url
879 const pdhWbPath = getNavUrl(pdhUrl, auth);
880 return pdhUrl && pdhWbPath ? (
881 <Tooltip title={<>View collection in Workbench<br />{pdhUrl}</>}>
885 className={classes.keepLink}
896 const KeepUrlPath = withStyles(styles)(({ auth, res, pdh, classes }: KeepUrlProps & WithStyles<CssRules>) => {
897 const keepUrl = getKeepUrl(res, pdh);
898 const keepUrlParts = keepUrl ? keepUrl.split("/") : [];
899 const keepUrlPath = keepUrlParts.length > 1 ? keepUrlParts.slice(1).join("/") : "";
901 const keepUrlPathNav = getKeepNavUrl(auth, res, pdh);
902 return keepUrlPathNav ? (
903 <Tooltip classes={{tooltip: classes.wrapTooltip}} title={<>View in keep-web<br />{keepUrlPath || "/"}</>}>
905 className={classes.keepLink}
906 href={keepUrlPathNav}
908 rel="noopener noreferrer"
918 const getKeepNavUrl = (auth: AuthState, file: File | Directory, pdh?: string): string => {
919 let keepUrl = getKeepUrl(file, pdh);
920 return getInlineFileUrl(
921 `${auth.config.keepWebServiceUrl}/c=${keepUrl}?api_token=${auth.apiToken}`,
922 auth.config.keepWebServiceUrl,
923 auth.config.keepWebInlineServiceUrl
927 const getImageUrl = (auth: AuthState, file: File, pdh?: string): string => {
928 const keepUrl = getKeepUrl(file, pdh);
929 return getInlineFileUrl(
930 `${auth.config.keepWebServiceUrl}/c=${keepUrl}?api_token=${auth.apiToken}`,
931 auth.config.keepWebServiceUrl,
932 auth.config.keepWebInlineServiceUrl
936 const isFileImage = (basename?: string): boolean => {
937 return basename ? (mime.getType(basename) || "").startsWith("image/") : false;
940 const isFileUrl = (location?: string): boolean =>
941 !!location && !KEEP_URL_REGEX.exec(location) && (location.startsWith("http://") || location.startsWith("https://"));
943 const normalizeDirectoryLocation = (directory: Directory): Directory => {
944 if (!directory.location) {
949 location: (directory.location || "").endsWith("/") ? directory.location : directory.location + "/",
953 const directoryToProcessIOValue = (directory: Directory, auth: AuthState, pdh?: string): ProcessIOValue => {
954 if (isExternalValue(directory)) {
955 return { display: <UnsupportedValue /> };
958 const normalizedDirectory = normalizeDirectoryLocation(directory);
963 res={normalizedDirectory}
970 res={normalizedDirectory}
977 type MuiLinkWithTooltipProps = WithStyles<CssRules> & React.PropsWithChildren<LinkProps>;
979 const MuiLinkWithTooltip = withStyles(styles)((props: MuiLinkWithTooltipProps) => (
980 <Tooltip title={props.title} classes={{tooltip: props.classes.wrapTooltip}}>
987 const fileToProcessIOValue = (file: File, secondary: boolean, auth: AuthState, pdh: string | undefined, mainFilePdh: string): ProcessIOValue => {
988 if (isExternalValue(file)) {
989 return { display: <UnsupportedValue /> };
992 if (isFileUrl(file.location)) {
999 title={file.location}
1002 </MuiLinkWithTooltip>
1008 const resourcePdh = getResourcePdhUrl(file, pdh);
1018 imageUrl: isFileImage(file.basename) ? getImageUrl(auth, file, pdh) : undefined,
1020 resourcePdh !== mainFilePdh ? (
1032 const isExternalValue = (val: any) => Object.keys(val).includes("$import") || Object.keys(val).includes("$include");
1034 export const EmptyValue = withStyles(styles)(({ classes }: WithStyles<CssRules>) => <span className={classes.emptyValue}>No value</span>);
1036 const UnsupportedValue = withStyles(styles)(({ classes }: WithStyles<CssRules>) => <span className={classes.emptyValue}>Cannot display value</span>);
1038 const SecretValue = withStyles(styles)(({ classes }: WithStyles<CssRules>) => <span className={classes.emptyValue}>Cannot display secret</span>);
1040 const UnsupportedValueChip = withStyles(styles)(({ classes }: WithStyles<CssRules>) => (
1043 label={"Cannot display value"}