554ae42631d5621ddf9f81646b89261e79cd615b
[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     Grid,
11     Tooltip,
12     Typography,
13     Card,
14     CardHeader
15 } from '@material-ui/core';
16 import { connect, DispatchProp } from "react-redux";
17 import { RouteComponentProps } from 'react-router';
18 import { ArvadosTheme } from 'common/custom-theme';
19 import { RootState } from 'store/store';
20 import { WorkflowIcon } from 'components/icon/icon';
21 import {
22     WorkflowResource, parseWorkflowDefinition, getWorkflowInputs,
23     getWorkflowOutputs, getWorkflow
24 } from 'models/workflow';
25 import { ProcessOutputCollectionFiles } from 'views/process-panel/process-output-collection-files';
26 import { WorkflowDetailsAttributes } from 'views-components/details-panel/workflow-details';
27 import { getResource } from 'store/resources/resources';
28 import { openContextMenu, resourceUuidToContextMenuKind } from 'store/context-menu/context-menu-actions';
29 import { MPVContainer, MPVPanelContent, MPVPanelState } from 'components/multi-panel-view/multi-panel-view';
30 import { ProcessIOCard, ProcessIOCardType, ProcessIOParameter } from 'views/process-panel/process-io-card';
31 import { formatInputData, formatOutputData } from 'store/process-panel/process-panel-actions';
32 import { DetailsAttribute } from 'components/details-attribute/details-attribute';
33 import { getPropertyChip } from "views-components/resource-properties-form/property-chip";
34
35 type CssRules = 'root'
36     | 'button'
37     | 'infoCard'
38     | 'propertiesCard'
39     | 'filesCard'
40     | 'iconHeader'
41     | 'tag'
42     | 'label'
43     | 'value'
44     | 'link'
45     | 'centeredLabel'
46     | 'warningLabel'
47     | 'collectionName'
48     | 'readOnlyIcon'
49     | 'header'
50     | 'title'
51     | 'avatar'
52     | 'propertyTag';
53
54 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
55     root: {
56         width: '100%',
57     },
58     button: {
59         cursor: 'pointer'
60     },
61     infoCard: {
62         paddingLeft: theme.spacing.unit * 2,
63         paddingRight: theme.spacing.unit * 2,
64         paddingBottom: theme.spacing.unit * 2,
65     },
66     propertiesCard: {
67         padding: 0,
68     },
69     filesCard: {
70         padding: 0,
71     },
72     iconHeader: {
73         fontSize: '1.875rem',
74         color: theme.customs.colors.greyL
75     },
76     tag: {
77         marginRight: theme.spacing.unit / 2,
78         marginBottom: theme.spacing.unit / 2
79     },
80     label: {
81         fontSize: '0.875rem',
82     },
83     centeredLabel: {
84         fontSize: '0.875rem',
85         textAlign: 'center'
86     },
87     warningLabel: {
88         fontStyle: 'italic'
89     },
90     collectionName: {
91         flexDirection: 'column',
92     },
93     value: {
94         textTransform: 'none',
95         fontSize: '0.875rem'
96     },
97     link: {
98         fontSize: '0.875rem',
99         color: theme.palette.primary.main,
100         '&:hover': {
101             cursor: 'pointer'
102         }
103     },
104     readOnlyIcon: {
105         marginLeft: theme.spacing.unit,
106         fontSize: 'small',
107     },
108     header: {
109         paddingTop: theme.spacing.unit,
110         paddingBottom: theme.spacing.unit,
111     },
112     title: {
113         overflow: 'hidden',
114         paddingTop: theme.spacing.unit * 0.5,
115         color: theme.customs.colors.green700,
116     },
117     avatar: {
118         alignSelf: 'flex-start',
119         paddingTop: theme.spacing.unit * 0.5
120     },
121     propertyTag: {
122         marginRight: theme.spacing.unit / 2,
123         marginBottom: theme.spacing.unit / 2
124     },
125 });
126
127 interface RegisteredWorkflowPanelDataProps {
128     item: WorkflowResource;
129     workflowCollection: string;
130     inputParams: ProcessIOParameter[];
131     outputParams: ProcessIOParameter[];
132     gitprops: { [key: string]: string; };
133 }
134
135 type RegisteredWorkflowPanelProps = RegisteredWorkflowPanelDataProps & DispatchProp & WithStyles<CssRules>
136
137 export const RegisteredWorkflowPanel = withStyles(styles)(connect(
138     (state: RootState, props: RouteComponentProps<{ id: string }>) => {
139         const item = getResource<WorkflowResource>(props.match.params.id)(state.resources);
140         let inputParams: ProcessIOParameter[] = [];
141         let outputParams: ProcessIOParameter[] = [];
142         let workflowCollection = "";
143         const gitprops: { [key: string]: string; } = {};
144         if (item) {
145             // parse definition
146             const wfdef = parseWorkflowDefinition(item);
147
148             const inputs = getWorkflowInputs(wfdef);
149             if (inputs) {
150                 inputs.forEach(elm => {
151                     if (elm.default !== undefined && elm.default !== null) {
152                         elm.value = elm.default;
153                     }
154                 });
155                 inputParams = formatInputData(inputs, state.auth);
156             }
157
158             const outputs = getWorkflowOutputs(wfdef);
159             if (outputs) {
160                 outputParams = formatOutputData(outputs, {}, undefined, state.auth);
161             }
162
163             const wf = getWorkflow(wfdef);
164             if (wf) {
165                 const REGEX = /keep:([0-9a-f]{32}\+\d+)\/.*/;
166                 if (wf["steps"]) {
167                     workflowCollection = wf["steps"][0].run.match(REGEX)[1];
168                 }
169             }
170
171             for (const elm in wfdef) {
172                 if (elm.startsWith("http://arvados.org/cwl#git")) {
173                     gitprops[elm.substr(23)] = wfdef[elm]
174                 }
175             }
176
177         }
178         return { item, inputParams, outputParams, workflowCollection, gitprops };
179     })(
180         class extends React.Component<RegisteredWorkflowPanelProps> {
181             render() {
182                 const { classes, item, inputParams, outputParams, workflowCollection, gitprops, dispatch } = this.props;
183                 const panelsData: MPVPanelState[] = [
184                     { name: "Details" },
185                     { name: "Inputs" },
186                     { name: "Outputs" },
187                     { name: "Files" },
188                 ];
189                 return item
190                     ? <MPVContainer className={classes.root} spacing={8} direction="column" justify-content="flex-start" wrap="nowrap" panelStates={panelsData}>
191                         <MPVPanelContent xs="auto" data-cy='registered-workflow-info-panel'>
192                             <Card className={classes.infoCard}>
193                                 <CardHeader
194                                     className={classes.header}
195                                     classes={{
196                                         content: classes.title,
197                                         avatar: classes.avatar,
198                                     }}
199                                     avatar={<WorkflowIcon className={classes.iconHeader} />}
200                                     title={
201                                         <Tooltip title={item.name} placement="bottom-start">
202                                             <Typography noWrap variant='h6'>
203                                                 {item.name}
204                                             </Typography>
205                                         </Tooltip>
206                                     }
207                                     subheader={
208                                         <Tooltip title={item.description || '(no-description)'} placement="bottom-start">
209                                             <Typography noWrap variant='body1' color='inherit'>
210                                                 {item.description || '(no-description)'}
211                                             </Typography>
212                                         </Tooltip>}
213                                 />
214
215                                 <Grid container justify="space-between">
216                                     <Grid item xs={12}>
217                                         <WorkflowDetailsAttributes workflow={item} />
218                                     </Grid>
219
220                                     <Grid item xs={12} md={12}>
221                                         <DetailsAttribute label='Properties' />
222                                         {Object.keys(gitprops).map(k =>
223                                             getPropertyChip(k, gitprops[k], undefined, classes.propertyTag))}
224                                     </Grid>
225                                 </Grid>
226                             </Card>
227                         </MPVPanelContent>
228                         <MPVPanelContent forwardProps xs data-cy="process-inputs">
229                             <ProcessIOCard
230                                 label={ProcessIOCardType.INPUT}
231                                 params={inputParams}
232                                 raw={{}}
233                                 showParams={true}
234                             />
235                         </MPVPanelContent>
236                         <MPVPanelContent forwardProps xs data-cy="process-outputs">
237                             <ProcessIOCard
238                                 label={ProcessIOCardType.OUTPUT}
239                                 params={outputParams}
240                                 raw={{}}
241                                 showParams={true}
242                             />
243                         </MPVPanelContent>
244                         <MPVPanelContent xs>
245                             <Card className={classes.filesCard}>
246                                 <ProcessOutputCollectionFiles isWritable={false} currentItemUuid={workflowCollection} />
247                             </Card>
248                         </MPVPanelContent>
249                     </MPVContainer>
250                     : null;
251             }
252
253             handleContextMenu = (event: React.MouseEvent<any>) => {
254                 const { uuid, ownerUuid, name, description,
255                     kind } = this.props.item;
256                 const menuKind = this.props.dispatch<any>(resourceUuidToContextMenuKind(uuid));
257                 const resource = {
258                     uuid,
259                     ownerUuid,
260                     name,
261                     description,
262                     kind,
263                     menuKind,
264                 };
265                 // Avoid expanding/collapsing the panel
266                 event.stopPropagation();
267                 this.props.dispatch<any>(openContextMenu(event, resource));
268             }
269         }
270     )
271 );