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