21067: Better handling of missing output/logs on process panel.
[arvados-workbench2.git] / src / views / workflow-panel / registered-workflow-panel.tsx
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import React from 'react';
6 import {
7     StyleRulesCallback,
8     WithStyles,
9     withStyles,
10     Tooltip,
11     Typography,
12     Card,
13     CardHeader,
14     CardContent,
15     IconButton,
16     Grid
17 } from '@material-ui/core';
18 import { connect, DispatchProp } from "react-redux";
19 import { RouteComponentProps } from 'react-router';
20 import { ArvadosTheme } from 'common/custom-theme';
21 import { RootState } from 'store/store';
22 import { WorkflowIcon, MoreVerticalIcon } from 'components/icon/icon';
23 import { WorkflowResource } from 'models/workflow';
24 import { ProcessOutputCollectionFiles } from 'views/process-panel/process-output-collection-files';
25 import { WorkflowDetailsAttributes, RegisteredWorkflowPanelDataProps, getRegisteredWorkflowPanelData } from 'views-components/details-panel/workflow-details';
26 import { getResource } from 'store/resources/resources';
27 import { openContextMenu, resourceUuidToContextMenuKind } from 'store/context-menu/context-menu-actions';
28 import { MPVContainer, MPVPanelContent, MPVPanelState } from 'components/multi-panel-view/multi-panel-view';
29 import { ProcessIOCard, ProcessIOCardType } from 'views/process-panel/process-io-card';
30 import { DefaultView } from "components/default-view/default-view";
31
32 type CssRules = 'root'
33     | 'button'
34     | 'infoCard'
35     | 'propertiesCard'
36     | 'filesCard'
37     | 'iconHeader'
38     | 'tag'
39     | 'label'
40     | 'value'
41     | 'link'
42     | 'centeredLabel'
43     | 'warningLabel'
44     | 'collectionName'
45     | 'readOnlyIcon'
46     | 'header'
47     | 'title'
48     | 'avatar'
49     | 'content';
50
51 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
52     root: {
53         width: '100%',
54     },
55     button: {
56         cursor: 'pointer'
57     },
58     infoCard: {
59     },
60     propertiesCard: {
61         padding: 0,
62     },
63     filesCard: {
64         padding: 0,
65     },
66     iconHeader: {
67         fontSize: '1.875rem',
68         color: theme.customs.colors.greyL
69     },
70     tag: {
71         marginRight: theme.spacing.unit / 2,
72         marginBottom: theme.spacing.unit / 2
73     },
74     label: {
75         fontSize: '0.875rem',
76     },
77     centeredLabel: {
78         fontSize: '0.875rem',
79         textAlign: 'center'
80     },
81     warningLabel: {
82         fontStyle: 'italic'
83     },
84     collectionName: {
85         flexDirection: 'column',
86     },
87     value: {
88         textTransform: 'none',
89         fontSize: '0.875rem'
90     },
91     link: {
92         fontSize: '0.875rem',
93         color: theme.palette.primary.main,
94         '&:hover': {
95             cursor: 'pointer'
96         }
97     },
98     readOnlyIcon: {
99         marginLeft: theme.spacing.unit,
100         fontSize: 'small',
101     },
102     header: {
103         paddingTop: theme.spacing.unit,
104         paddingBottom: theme.spacing.unit,
105     },
106     title: {
107         overflow: 'hidden',
108         paddingTop: theme.spacing.unit * 0.5,
109         color: theme.customs.colors.green700,
110     },
111     avatar: {
112         alignSelf: 'flex-start',
113         paddingTop: theme.spacing.unit * 0.5
114     },
115     content: {
116         padding: theme.spacing.unit * 1.0,
117         paddingTop: theme.spacing.unit * 0.5,
118         '&:last-child': {
119             paddingBottom: theme.spacing.unit * 1,
120         }
121     }
122 });
123
124 type RegisteredWorkflowPanelProps = RegisteredWorkflowPanelDataProps & DispatchProp & WithStyles<CssRules>
125
126 export const RegisteredWorkflowPanel = withStyles(styles)(connect(
127     (state: RootState, props: RouteComponentProps<{ id: string }>) => {
128         const item = getResource<WorkflowResource>(props.match.params.id)(state.resources);
129         if (item) {
130             return getRegisteredWorkflowPanelData(item, state.auth);
131         }
132         return { item, inputParams: [], outputParams: [], workflowCollection: "", gitprops: {} };
133     })(
134         class extends React.Component<RegisteredWorkflowPanelProps> {
135             render() {
136                 const { classes, item, inputParams, outputParams, workflowCollection } = this.props;
137                 const panelsData: MPVPanelState[] = [
138                     { name: "Details" },
139                     { name: "Inputs" },
140                     { name: "Outputs" },
141                     { name: "Files" },
142                 ];
143                 return item
144                     ? <MPVContainer className={classes.root} spacing={8} direction="column" justify-content="flex-start" wrap="nowrap" panelStates={panelsData}>
145                         <MPVPanelContent xs="auto" data-cy='registered-workflow-info-panel'>
146                             <Card className={classes.infoCard}>
147                                 <CardHeader
148                                     className={classes.header}
149                                     classes={{
150                                         content: classes.title,
151                                         avatar: classes.avatar,
152                                     }}
153                                     avatar={<WorkflowIcon className={classes.iconHeader} />}
154                                     title={
155                                         <Tooltip title={item.name} placement="bottom-start">
156                                             <Typography noWrap variant='h6'>
157                                                 {item.name}
158                                             </Typography>
159                                         </Tooltip>
160                                     }
161                                     subheader={
162                                         <Tooltip title={item.description || '(no-description)'} placement="bottom-start">
163                                             <Typography noWrap variant='body1' color='inherit'>
164                                                 {item.description || '(no-description)'}
165                                             </Typography>
166                                         </Tooltip>}
167                                     action={
168                                         <Tooltip title="More options" disableFocusListener>
169                                             <IconButton
170                                                 aria-label="More options"
171                                                 onClick={event => this.handleContextMenu(event)}>
172                                                 <MoreVerticalIcon />
173                                             </IconButton>
174                                         </Tooltip>}
175
176                                 />
177
178                                 <CardContent className={classes.content}>
179                                     <WorkflowDetailsAttributes workflow={item} />
180                                 </CardContent>
181                             </Card>
182                         </MPVPanelContent>
183                         <MPVPanelContent forwardProps xs data-cy="process-inputs">
184                             <ProcessIOCard
185                                 label={ProcessIOCardType.INPUT}
186                                 params={inputParams}
187                                 raw={{}}
188                                 showParams={true}
189                             />
190                         </MPVPanelContent>
191                         <MPVPanelContent forwardProps xs data-cy="process-outputs">
192                             <ProcessIOCard
193                                 label={ProcessIOCardType.OUTPUT}
194                                 params={outputParams}
195                                 raw={{}}
196                                 showParams={true}
197                             />
198                         </MPVPanelContent>
199                         <MPVPanelContent xs>
200                             <Card className={classes.filesCard}>
201                                 <ProcessOutputCollectionFiles isWritable={false} currentItemUuid={workflowCollection} />
202                             </Card>
203                         </MPVPanelContent>
204                     </MPVContainer>
205                     : <Grid
206                         container
207                         alignItems="center"
208                         justify="center"
209                         style={{ minHeight: "100%" }}>
210                         <DefaultView
211                             icon={WorkflowIcon}
212                             messages={["Workflow not found"]}
213                         />
214                     </Grid>;
215             }
216
217             handleContextMenu = (event: React.MouseEvent<any>) => {
218                 const { uuid, ownerUuid, name, description,
219                     kind } = this.props.item;
220                 const menuKind = this.props.dispatch<any>(resourceUuidToContextMenuKind(uuid));
221                 const resource = {
222                     uuid,
223                     ownerUuid,
224                     name,
225                     description,
226                     kind,
227                     menuKind,
228                 };
229                 // Avoid expanding/collapsing the panel
230                 event.stopPropagation();
231                 this.props.dispatch<any>(openContextMenu(event, resource));
232             }
233         }
234     )
235 );