Merge branch '19315-process-runtime-user-rebase1' into main. Closes #19315
authorStephen Smith <stephen@curii.com>
Mon, 31 Oct 2022 13:55:56 +0000 (09:55 -0400)
committerStephen Smith <stephen@curii.com>
Mon, 31 Oct 2022 13:55:56 +0000 (09:55 -0400)
Arvados-DCO-1.1-Signed-off-by: Stephen Smith <stephen@curii.com>

src/components/data-explorer/data-explorer.tsx
src/components/icon/icon.tsx
src/components/multi-panel-view/multi-panel-view.test.tsx
src/components/multi-panel-view/multi-panel-view.tsx
src/components/search-input/search-input.tsx
src/views/process-panel/process-io-card.tsx
src/views/process-panel/process-log-card.tsx
src/views/subprocess-panel/subprocess-panel-root.tsx

index 40617f73336b197149790264b2d379c4616d1e3f..c7a296a60f4aebe5d6ad946265484be9ae148620 100644 (file)
@@ -11,7 +11,13 @@ import { SearchInput } from 'components/search-input/search-input';
 import { ArvadosTheme } from "common/custom-theme";
 import { createTree } from 'models/tree';
 import { DataTableFilters } from 'components/data-table-filters/data-table-filters-tree';
-import { CloseIcon, IconType, MaximizeIcon, MoreOptionsIcon } from 'components/icon/icon';
+import {
+    CloseIcon,
+    IconType,
+    MaximizeIcon,
+    UnMaximizeIcon,
+    MoreOptionsIcon
+} from 'components/icon/icon';
 import { PaperProps } from '@material-ui/core/Paper';
 import { MPVPanelProps } from 'components/multi-panel-view/multi-panel-view';
 
@@ -19,11 +25,11 @@ type CssRules = 'searchBox' | 'headerMenu' | "toolbar" | "footer" | "root" | 'mo
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     searchBox: {
-        paddingBottom: theme.spacing.unit * 2
+        paddingBottom: 0,
     },
     toolbar: {
-        paddingTop: theme.spacing.unit,
-        paddingRight: theme.spacing.unit * 2,
+        paddingTop: 0,
+        paddingRight: theme.spacing.unit,
     },
     footer: {
         overflow: 'auto'
@@ -36,8 +42,8 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     },
     title: {
         display: 'inline-block',
-        paddingLeft: theme.spacing.unit * 3,
-        paddingTop: theme.spacing.unit * 3,
+        paddingLeft: theme.spacing.unit * 2,
+        paddingTop: theme.spacing.unit * 2,
         fontSize: '18px'
     },
     dataTable: {
@@ -49,7 +55,7 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     },
     headerMenu: {
         float: 'right',
-        display: 'inline-block'
+        display: 'inline-block',
     }
 });
 
@@ -152,7 +158,7 @@ export const DataExplorer = withStyles(styles)(
                 items, itemsAvailable, onRowClick, onRowDoubleClick, classes,
                 defaultViewIcon, defaultViewMessages, hideColumnSelector, actions, paperProps, hideSearchInput,
                 paperKey, fetchMode, currentItemUuid, title,
-                doHidePanel, doMaximizePanel, panelName, panelMaximized, elementPath
+                doHidePanel, doMaximizePanel, doUnMaximizePanel, panelName, panelMaximized, elementPath
             } = this.props;
 
             return <Paper className={classes.root} {...paperProps} key={paperKey} data-cy={this.props["data-cy"]}>
@@ -163,26 +169,28 @@ export const DataExplorer = withStyles(styles)(
                             (!hideColumnSelector || !hideSearchInput || !!actions) &&
                             <Grid className={classes.headerMenu} item xs>
                                 <Toolbar className={classes.toolbar}>
-                                    <Grid container justify="space-between" wrap="nowrap" alignItems="center">
-                                        {!hideSearchInput && <div className={classes.searchBox}>
-                                            {!hideSearchInput && <SearchInput
-                                                label={searchLabel}
-                                                value={searchValue}
-                                                selfClearProp={currentItemUuid}
-                                                onSearch={onSearch} />}
-                                        </div>}
-                                        {actions}
-                                        {!hideColumnSelector && <ColumnSelector
-                                            columns={columns}
-                                            onColumnToggle={onColumnToggle} />}
-                                    </Grid>
+                                    {!hideSearchInput && <div className={classes.searchBox}>
+                                        {!hideSearchInput && <SearchInput
+                                            label={searchLabel}
+                                            value={searchValue}
+                                            selfClearProp={currentItemUuid}
+                                            onSearch={onSearch} />}
+                                    </div>}
+                                    {actions}
+                                    {!hideColumnSelector && <ColumnSelector
+                                        columns={columns}
+                                        onColumnToggle={onColumnToggle} />}
+                                    { 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 onClick={doHidePanel}><CloseIcon /></IconButton>
+                                            <IconButton disabled={panelMaximized} onClick={doHidePanel}><CloseIcon /></IconButton>
                                         </Tooltip> }
                                 </Toolbar>
                             </Grid>
index daa776a06c3b269d5f51dbbf2aec73400ec0fef1..a6c118fc57bb07a3c21eb1d72948a441a5623d93 100644 (file)
@@ -66,7 +66,8 @@ import Computer from '@material-ui/icons/Computer';
 import WrapText from '@material-ui/icons/WrapText';
 import TextIncrease from '@material-ui/icons/ZoomIn';
 import TextDecrease from '@material-ui/icons/ZoomOut';
-import CropFreeSharp from '@material-ui/icons/CropFreeSharp';
+import FullscreenSharp from '@material-ui/icons/FullscreenSharp';
+import FullscreenExitSharp from '@material-ui/icons/FullscreenExitSharp';
 import ExitToApp from '@material-ui/icons/ExitToApp';
 import CheckCircleOutline from '@material-ui/icons/CheckCircleOutline';
 import RemoveCircleOutline from '@material-ui/icons/RemoveCircleOutline';
@@ -167,7 +168,8 @@ export const FileInputIcon: IconType = (props) => <InsertDriveFile {...props} />
 export const KeyIcon: IconType = (props) => <VpnKey {...props} />;
 export const LogIcon: IconType = (props) => <SettingsEthernet {...props} />;
 export const MailIcon: IconType = (props) => <Mail {...props} />;
-export const MaximizeIcon: IconType = (props) => <CropFreeSharp {...props} />;
+export const MaximizeIcon: IconType = (props) => <FullscreenSharp {...props} />;
+export const UnMaximizeIcon: IconType = (props) => <FullscreenExitSharp {...props} />;
 export const MoreOptionsIcon: IconType = (props) => <MoreVert {...props} />;
 export const MoveToIcon: IconType = (props) => <Input {...props} />;
 export const NewProjectIcon: IconType = (props) => <CreateNewFolder {...props} />;
index d690e82fa7ecfbd33c92cb98c2b2229e3d6a6dc2..3f4911c2258780963b32dea7c01645078b3e1057 100644 (file)
@@ -10,7 +10,7 @@ import { Button } from "@material-ui/core";
 
 configure({ adapter: new Adapter() });
 
-const PanelMock = ({panelName, panelMaximized, doHidePanel, doMaximizePanel, panelIlluminated, panelRef, children, ...rest}) =>
+const PanelMock = ({panelName, panelMaximized, doHidePanel, doMaximizePanel, doUnMaximizePanel, panelIlluminated, panelRef, children, ...rest}) =>
     <div {...rest}>{children}</div>;
 
 describe('<MPVContainer />', () => {
index f4c3f3ba44f5624b83a0ebaf299239957d84263e..f0cbcf56be67b3e826150db35f9fccd8dc6c3187 100644 (file)
@@ -48,14 +48,15 @@ interface MPVHideablePanelDataProps {
 interface MPVHideablePanelActionProps {
     doHidePanel: () => void;
     doMaximizePanel: () => void;
+    doUnMaximizePanel: () => void;
 }
 
 type MPVHideablePanelProps = MPVHideablePanelDataProps & MPVHideablePanelActionProps;
 
-const MPVHideablePanel = ({doHidePanel, doMaximizePanel, name, visible, maximized, illuminated, ...props}: MPVHideablePanelProps) =>
+const MPVHideablePanel = ({doHidePanel, doMaximizePanel, doUnMaximizePanel, name, visible, maximized, illuminated, ...props}: MPVHideablePanelProps) =>
     visible
     ? <>
-        {React.cloneElement((props.children as ReactElement), { doHidePanel, doMaximizePanel, panelName: name, panelMaximized: maximized, panelIlluminated: illuminated, panelRef: props.panelRef })}
+        {React.cloneElement((props.children as ReactElement), { doHidePanel, doMaximizePanel, doUnMaximizePanel, panelName: name, panelMaximized: maximized, panelIlluminated: illuminated, panelRef: props.panelRef })}
     </>
     : null;
 
@@ -71,6 +72,7 @@ interface MPVPanelDataProps {
 interface MPVPanelActionProps {
     doHidePanel?: () => void;
     doMaximizePanel?: () => void;
+    doUnMaximizePanel?: () => void;
 }
 
 // Props received by panel implementors
@@ -79,12 +81,12 @@ export type MPVPanelProps = MPVPanelDataProps & MPVPanelActionProps;
 type MPVPanelContentProps = {children: ReactElement} & MPVPanelProps & GridProps;
 
 // Grid item compatible component for layout and MPV props passing
-export const MPVPanelContent = ({doHidePanel, doMaximizePanel, panelName,
+export const MPVPanelContent = ({doHidePanel, doMaximizePanel, doUnMaximizePanel, panelName,
     panelMaximized, panelIlluminated, panelRef, forwardProps, maxHeight,
     ...props}: MPVPanelContentProps) => {
     useEffect(() => {
         if (panelRef && panelRef.current) {
-            panelRef.current.scrollIntoView({behavior: 'smooth'});
+            panelRef.current.scrollIntoView({alignToTop: true});
         }
     }, [panelRef]);
 
@@ -96,7 +98,7 @@ export const MPVPanelContent = ({doHidePanel, doMaximizePanel, panelName,
         <span ref={panelRef} /> {/* Element to scroll to when the panel is selected */}
         <Paper style={{height: '100%'}} elevation={panelIlluminated ? 8 : 0}>
             { forwardProps
-                ? React.cloneElement(props.children, { doHidePanel, doMaximizePanel, panelName, panelMaximized })
+                ? React.cloneElement(props.children, { doHidePanel, doMaximizePanel, doUnMaximizePanel, panelName, panelMaximized })
                 : props.children }
         </Paper>
     </Grid>;
@@ -118,11 +120,12 @@ const MPVContainerComponent = ({children, panelStates, classes, ...props}: MPVCo
     } else if (!isArray(children)) {
         children = [children];
     }
-    const visibility = (children as ReactNodeArray).map((_, idx) =>
+    const initialVisibility = (children as ReactNodeArray).map((_, idx) =>
         !panelStates || // if panelStates wasn't passed, default to all visible panels
             (panelStates[idx] &&
                 (panelStates[idx].visible || panelStates[idx].visible === undefined)));
-    const [panelVisibility, setPanelVisibility] = useState<boolean[]>(visibility);
+    const [panelVisibility, setPanelVisibility] = useState<boolean[]>(initialVisibility);
+    const [previousPanelVisibility, setPreviousPanelVisibility] = useState<boolean[]>(initialVisibility);
     const [highlightedPanel, setHighlightedPanel] = useState<number>(-1);
     const [selectedPanel, setSelectedPanel] = useState<number>(-1);
     const panelRef = useRef<any>(null);
@@ -133,6 +136,7 @@ const MPVContainerComponent = ({children, panelStates, classes, ...props}: MPVCo
     if (isArray(children)) {
         for (let idx = 0; idx < children.length; idx++) {
             const showFn = (idx: number) => () => {
+                setPreviousPanelVisibility(initialVisibility);
                 setPanelVisibility([
                     ...panelVisibility.slice(0, idx),
                     true,
@@ -141,6 +145,7 @@ const MPVContainerComponent = ({children, panelStates, classes, ...props}: MPVCo
                 setSelectedPanel(idx);
             };
             const hideFn = (idx: number) => () => {
+                setPreviousPanelVisibility(initialVisibility);
                 setPanelVisibility([
                     ...panelVisibility.slice(0, idx),
                     false,
@@ -148,13 +153,18 @@ const MPVContainerComponent = ({children, panelStates, classes, ...props}: MPVCo
                 ])
             };
             const maximizeFn = (idx: number) => () => {
+                setPreviousPanelVisibility(panelVisibility);
                 // Maximize X == hide all but X
                 setPanelVisibility([
                     ...panelVisibility.slice(0, idx).map(() => false),
                     true,
                     ...panelVisibility.slice(idx+1).map(() => false),
-                ])
+                ]);
             };
+            const unMaximizeFn = (idx: number) => () => {
+                setPanelVisibility(previousPanelVisibility);
+                setSelectedPanel(idx);
+            }
             const panelName = panelStates === undefined
                 ? `Panel ${idx+1}`
                 : (panelStates[idx] && panelStates[idx].name) || `Panel ${idx+1}`;
@@ -188,7 +198,7 @@ const MPVContainerComponent = ({children, panelStates, classes, ...props}: MPVCo
                 <MPVHideablePanel key={idx} visible={panelVisibility[idx]} name={panelName}
                     panelRef={(idx === selectedPanel) ? panelRef : undefined}
                     maximized={panelIsMaximized} illuminated={idx === highlightedPanel}
-                    doHidePanel={hideFn(idx)} doMaximizePanel={maximizeFn(idx)}>
+                    doHidePanel={hideFn(idx)} doMaximizePanel={maximizeFn(idx)} doUnMaximizePanel={panelIsMaximized ? unMaximizeFn(idx) : () => null}>
                     {children[idx]}
                 </MPVHideablePanel>;
             panels = [...panels, aPanel];
index 8d86307ab71ac072ea676173b6098bcf5252c0d3..6d85ed22ec7cb97eaf477e57a1d7942ed7a56fff 100644 (file)
@@ -3,35 +3,16 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import React, {useState, useEffect} from 'react';
-import { IconButton, StyleRulesCallback, withStyles, WithStyles, FormControl, InputLabel, Input, InputAdornment, Tooltip } from '@material-ui/core';
+import {
+    IconButton,
+    FormControl,
+    InputLabel,
+    Input,
+    InputAdornment,
+    Tooltip,
+} from '@material-ui/core';
 import SearchIcon from '@material-ui/icons/Search';
 
-type CssRules = 'container' | 'input' | 'button';
-
-const styles: StyleRulesCallback<CssRules> = theme => {
-    return {
-        container: {
-            position: 'relative',
-            width: '100%'
-        },
-        input: {
-            border: 'none',
-            borderRadius: theme.spacing.unit / 4,
-            boxSizing: 'border-box',
-            padding: theme.spacing.unit,
-            paddingRight: theme.spacing.unit * 4,
-            width: '100%',
-        },
-        button: {
-            position: 'absolute',
-            top: theme.spacing.unit / 2,
-            right: theme.spacing.unit / 2,
-            width: theme.spacing.unit * 3,
-            height: theme.spacing.unit * 3
-        }
-    };
-};
-
 interface SearchInputDataProps {
     value: string;
     label?: string;
@@ -43,11 +24,11 @@ interface SearchInputActionProps {
     debounce?: number;
 }
 
-type SearchInputProps = SearchInputDataProps & SearchInputActionProps & WithStyles<CssRules>;
+type SearchInputProps = SearchInputDataProps & SearchInputActionProps;
 
 export const DEFAULT_SEARCH_DEBOUNCE = 1000;
 
-const SearchInputComponent = (props: SearchInputProps) => {
+export const SearchInput = (props: SearchInputProps) => {
     const [timeout, setTimeout] = useState(0);
     const [value, setValue] = useState("");
     const [label, setLabel] = useState("Search");
@@ -114,6 +95,4 @@ const SearchInputComponent = (props: SearchInputProps) => {
                 } />
         </FormControl>
     </form>;
-}
-
-export const SearchInput = withStyles(styles)(SearchInputComponent);
\ No newline at end of file
+};
index 5fd444b5e53fa5776b58813ce623e5d62f7b3097..904276595361360f0069cbd7f26e40690f6002ea 100644 (file)
@@ -27,7 +27,15 @@ import {
     CircularProgress,
 } from '@material-ui/core';
 import { ArvadosTheme } from 'common/custom-theme';
-import { CloseIcon, ImageIcon, InputIcon, ImageOffIcon, OutputIcon, MaximizeIcon } from 'components/icon/icon';
+import {
+    CloseIcon,
+    ImageIcon,
+    InputIcon,
+    ImageOffIcon,
+    OutputIcon,
+    MaximizeIcon,
+    UnMaximizeIcon
+} from 'components/icon/icon';
 import { MPVPanelProps } from 'components/multi-panel-view/multi-panel-view';
 import {
   BooleanCommandInputParameter,
@@ -227,7 +235,7 @@ const mapDispatchToProps = (dispatch: Dispatch): ProcessIOCardActionProps => ({
 type ProcessIOCardProps = ProcessIOCardDataProps & ProcessIOCardActionProps & WithStyles<CssRules> & MPVPanelProps;
 
 export const ProcessIOCard = withStyles(styles)(connect(null, mapDispatchToProps)(
-    ({ classes, label, params, raw, mounts, outputUuid, doHidePanel, doMaximizePanel, panelMaximized, panelName, process, navigateTo }: ProcessIOCardProps) => {
+    ({ classes, label, params, raw, mounts, outputUuid, doHidePanel, doMaximizePanel, doUnMaximizePanel, panelMaximized, panelName, process, navigateTo }: ProcessIOCardProps) => {
         const [mainProcTabState, setMainProcTabState] = useState(0);
         const handleMainProcTabChange = (event: React.MouseEvent<HTMLElement>, value: number) => {
             setMainProcTabState(value);
@@ -260,13 +268,17 @@ export const ProcessIOCard = withStyles(styles)(connect(null, mapDispatchToProps
                         { 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 onClick={doHidePanel}><CloseIcon /></IconButton>
+                            <IconButton disabled={panelMaximized} onClick={doHidePanel}><CloseIcon /></IconButton>
                         </Tooltip> }
                     </div>
                 } />
index 936b31a5497612999aab5a340ab33cf8bd7b04f4..03739699b09c8f15d2b58cf3499ce78d6972f2d6 100644 (file)
@@ -22,6 +22,7 @@ import {
     CopyIcon,
     LogIcon,
     MaximizeIcon,
+    UnMaximizeIcon,
     TextDecreaseIcon,
     TextIncreaseIcon,
     WordWrapOffIcon,
@@ -92,7 +93,7 @@ type ProcessLogsCardProps = ProcessLogsCardDataProps
 export const ProcessLogsCard = withStyles(styles)(
     ({ classes, process, filters, selectedFilter, lines,
         onLogFilterChange, navigateToLog, onCopy,
-        doHidePanel, doMaximizePanel, panelMaximized, panelName }: ProcessLogsCardProps) => {
+        doHidePanel, doMaximizePanel, doUnMaximizePanel, panelMaximized, panelName }: ProcessLogsCardProps) => {
         const [wordWrap, setWordWrap] = useState<boolean>(true);
         const [fontSize, setFontSize] = useState<number>(3);
         const fontBaseSize = 10;
@@ -144,15 +145,18 @@ export const ProcessLogsCard = withStyles(styles)(
                                 </IconButton>
                             </Tooltip>
                         </Grid>
+                        { 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 && <Grid item>
-                            <Tooltip title={`Close ${panelName || 'panel'}`} disableFocusListener>
-                                <IconButton onClick={doHidePanel}><CloseIcon /></IconButton>
-                            </Tooltip>
-                        </Grid> }
+                        { doHidePanel &&
+                        <Tooltip title={`Close ${panelName || 'panel'}`} disableFocusListener>
+                            <IconButton disabled={panelMaximized} onClick={doHidePanel}><CloseIcon /></IconButton>
+                        </Tooltip> }
                     </Grid>}
                     title={
                         <Typography noWrap variant='h6' className={classes.title}>
index d4ccae9c03abd5cf9cc58ae7bbf48f52e44b9569..c27673d268123e6ac7e4df421d5bfe217a57f887 100644 (file)
@@ -17,6 +17,21 @@ import { createTree } from 'models/tree';
 import { getInitialProcessStatusFilters } from 'store/resource-type-filters/resource-type-filters';
 import { ResourcesState } from 'store/resources/resources';
 import { MPVPanelProps } from 'components/multi-panel-view/multi-panel-view';
+import { StyleRulesCallback, Typography, WithStyles, withStyles } from '@material-ui/core';
+import { ArvadosTheme } from 'common/custom-theme';
+
+type CssRules = 'iconHeader' | 'cardHeader';
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+    iconHeader: {
+        fontSize: '1.875rem',
+        color: theme.customs.colors.green700,
+        marginRight: theme.spacing.unit * 2,
+    },
+    cardHeader: {
+        display: 'flex',
+    },
+});
 
 export enum SubprocessPanelColumnNames {
     NAME = "Name",
@@ -80,6 +95,18 @@ const DEFAULT_VIEW_MESSAGES = [
     'The current process may not have any or none matches current filtering.'
 ];
 
+type SubProcessesTitleProps = WithStyles<CssRules>;
+
+const SubProcessesTitle = withStyles(styles)(
+    ({classes}: SubProcessesTitleProps) =>
+        <div className={classes.cardHeader}>
+            <ProcessIcon className={classes.iconHeader} /><span></span>
+            <Typography noWrap variant='h6' color='inherit'>
+                Subprocesses
+            </Typography>
+        </div>
+);
+
 export const SubprocessPanelRoot = (props: SubprocessPanelProps & MPVPanelProps) => {
     return <DataExplorer
         id={SUBPROCESS_PANEL_ID}
@@ -91,6 +118,8 @@ export const SubprocessPanelRoot = (props: SubprocessPanelProps & MPVPanelProps)
         defaultViewMessages={DEFAULT_VIEW_MESSAGES}
         doHidePanel={props.doHidePanel}
         doMaximizePanel={props.doMaximizePanel}
+        doUnMaximizePanel={props.doUnMaximizePanel}
         panelMaximized={props.panelMaximized}
-        panelName={props.panelName} />;
+        panelName={props.panelName}
+        title={<SubProcessesTitle/>} />;
 };