Merge branch '13694-Data-operations-Project-creation' of git.curoverse.com:arvados...
[arvados.git] / src / views / project-panel / project-panel.tsx
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import * as React from 'react';
6 import { ProjectPanelItem } from './project-panel-item';
7 import { Grid, Typography, Button, Toolbar, StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core';
8 import { formatDate, formatFileSize } from '../../common/formatters';
9 import DataExplorer from "../../views-components/data-explorer/data-explorer";
10 import { ContextMenuAction } from '../../components/context-menu/context-menu';
11 import { DispatchProp, connect } from 'react-redux';
12 import { DataColumns } from '../../components/data-table/data-table';
13 import { RouteComponentProps } from 'react-router';
14 import { RootState } from '../../store/store';
15 import { ResourceKind } from '../../models/kinds';
16 import { DataTableFilterItem } from '../../components/data-table-filters/data-table-filters';
17 import { ContainerRequestState } from '../../models/container-request';
18 import { SortDirection } from '../../components/data-table/data-column';
19 import DialogProjectCreate from '../../components/dialog-create/dialog-project-create';
20 import { mockAnchorFromMouseEvent } from "../../components/popover/helpers";
21
22 export const PROJECT_PANEL_ID = "projectPanel";
23
24 export interface ProjectPanelFilter extends DataTableFilterItem {
25     type: ResourceKind | ContainerRequestState;
26 }
27
28 interface DataExplorerState<T> {
29     contextMenu: {
30         anchorEl?: HTMLElement;
31         item?: T;
32     };
33     open?: boolean;
34 }
35
36 type ProjectPanelProps = {
37     currentItemId: string,
38     onItemClick: (item: ProjectPanelItem) => void,
39     onItemRouteChange: (itemId: string) => void,
40     onContextMenu: (event: React.MouseEvent<HTMLElement>, item: ProjectPanelItem) => void;
41     handleCreationDialogOpen: () => void;
42     handleCreationDialogClose: () => void;
43     isCreationDialogOpen: boolean;
44 }
45     & DispatchProp
46     & WithStyles<CssRules>
47     & RouteComponentProps<{ id: string }>;
48
49 class ProjectPanel extends React.Component<ProjectPanelProps, DataExplorerState<any>> {
50     state: DataExplorerState<any> = {
51         contextMenu: {},
52         open: false,
53     };
54     
55     render() {
56         return <div>
57             <div className={this.props.classes.toolbar}>
58                 <Button color="primary" variant="raised" className={this.props.classes.button}>
59                     Create a collection
60                 </Button>
61                 <Button color="primary" variant="raised" className={this.props.classes.button}>
62                     Run a process
63                 </Button>
64                 <Button color="primary" onClick={this.props.handleCreationDialogOpen} variant="raised" className={this.props.classes.button}>
65                     New project
66                 </Button>
67                 <DialogProjectCreate open={this.props.isCreationDialogOpen} handleClose={this.props.handleCreationDialogClose}/>
68             </div>
69             <DataExplorer
70                 id={PROJECT_PANEL_ID}
71                 onRowClick={this.props.onItemClick}
72                 onContextMenu={this.props.onContextMenu} />;
73         </div>;
74     }
75
76     componentWillReceiveProps({ match, currentItemId }: ProjectPanelProps) {
77         if (match.params.id !== currentItemId) {
78             this.props.onItemRouteChange(match.params.id);
79         }
80     }
81
82     executeAction = (action: ContextMenuAction, item: ProjectPanelItem) => {
83         alert(`Executing ${action.name} on ${item.name}`);
84     }
85
86     openContextMenu = (event: React.MouseEvent<HTMLElement>, item: any) => {
87         event.preventDefault();
88         event.stopPropagation();
89         this.setState({
90             contextMenu: {
91                 anchorEl: mockAnchorFromMouseEvent(event),
92                 item
93             }
94         });
95     }
96
97     closeContextMenu = () => {
98         this.setState({ contextMenu: {} });
99     }
100
101 }
102
103 type CssRules = "toolbar" | "button";
104
105 const styles: StyleRulesCallback<CssRules> = theme => ({
106     toolbar: {
107         paddingBottom: theme.spacing.unit * 3,
108         textAlign: "right"
109     },
110     button: {
111         marginLeft: theme.spacing.unit
112     },
113 });
114
115 const renderName = (item: ProjectPanelItem) =>
116     <Grid
117         container
118         alignItems="center"
119         wrap="nowrap"
120         spacing={16}>
121         <Grid item>
122             {renderIcon(item)}
123         </Grid>
124         <Grid item>
125             <Typography color="primary">
126                 {item.name}
127             </Typography>
128         </Grid>
129     </Grid>;
130
131
132 const renderIcon = (item: ProjectPanelItem) => {
133     switch (item.kind) {
134         case ResourceKind.Project:
135             return <i className="fas fa-folder fa-lg" />;
136         case ResourceKind.Collection:
137             return <i className="fas fa-archive fa-lg" />;
138         case ResourceKind.Process:
139             return <i className="fas fa-cogs fa-lg" />;
140         default:
141             return <i />;
142     }
143 };
144
145 const renderDate = (date: string) =>
146     <Typography noWrap>
147         {formatDate(date)}
148     </Typography>;
149
150 const renderFileSize = (fileSize?: number) =>
151     <Typography noWrap>
152         {formatFileSize(fileSize)}
153     </Typography>;
154
155 const renderOwner = (owner: string) =>
156     <Typography noWrap color="primary">
157         {owner}
158     </Typography>;
159
160
161
162 const typeToLabel = (type: string) => {
163     switch (type) {
164         case ResourceKind.Collection:
165             return "Data collection";
166         case ResourceKind.Project:
167             return "Project";
168         case ResourceKind.Process:
169             return "Process";
170         default:
171             return "Unknown";
172     }
173 };
174
175 const renderType = (type: string) => {
176     return <Typography noWrap>
177         {typeToLabel(type)}
178     </Typography>;
179 };
180
181 const renderStatus = (item: ProjectPanelItem) =>
182     <Typography noWrap align="center">
183         {item.status || "-"}
184     </Typography>;
185
186 export enum ProjectPanelColumnNames {
187     Name = "Name",
188     Status = "Status",
189     Type = "Type",
190     Owner = "Owner",
191     FileSize = "File size",
192     LastModified = "Last modified"
193
194 }
195
196 export const columns: DataColumns<ProjectPanelItem, ProjectPanelFilter> = [{
197     name: ProjectPanelColumnNames.Name,
198     selected: true,
199     sortDirection: SortDirection.Asc,
200     render: renderName,
201     width: "450px"
202 }, {
203     name: "Status",
204     selected: true,
205     filters: [{
206         name: ContainerRequestState.Committed,
207         selected: true,
208         type: ContainerRequestState.Committed
209     }, {
210         name: ContainerRequestState.Final,
211         selected: true,
212         type: ContainerRequestState.Final
213     }, {
214         name: ContainerRequestState.Uncommitted,
215         selected: true,
216         type: ContainerRequestState.Uncommitted
217     }],
218     render: renderStatus,
219     width: "75px"
220 }, {
221     name: ProjectPanelColumnNames.Type,
222     selected: true,
223     filters: [{
224         name: typeToLabel(ResourceKind.Collection),
225         selected: true,
226         type: ResourceKind.Collection
227     }, {
228         name: typeToLabel(ResourceKind.Process),
229         selected: true,
230         type: ResourceKind.Process
231     }, {
232         name: typeToLabel(ResourceKind.Project),
233         selected: true,
234         type: ResourceKind.Project
235     }],
236     render: item => renderType(item.kind),
237     width: "125px"
238 }, {
239     name: ProjectPanelColumnNames.Owner,
240     selected: true,
241     render: item => renderOwner(item.owner),
242     width: "200px"
243 }, {
244     name: ProjectPanelColumnNames.FileSize,
245     selected: true,
246     render: item => renderFileSize(item.fileSize),
247     width: "50px"
248 }, {
249     name: ProjectPanelColumnNames.LastModified,
250     selected: true,
251     sortDirection: SortDirection.None,
252     render: item => renderDate(item.lastModified),
253     width: "150px"
254 }];
255
256
257 export default withStyles(styles)(
258     connect((state: RootState) => ({ currentItemId: state.projects.currentItemId }))(
259         ProjectPanel));