20000: Add test for process cancel button, rename Run Process to Run
[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 React from 'react';
6 import withStyles from "@material-ui/core/styles/withStyles";
7 import { DispatchProp, connect } from 'react-redux';
8 import { RouteComponentProps } from 'react-router';
9 import { StyleRulesCallback, WithStyles } from "@material-ui/core";
10
11 import { DataExplorer } from "views-components/data-explorer/data-explorer";
12 import { DataColumns } from 'components/data-table/data-table';
13 import { RootState } from 'store/store';
14 import { DataTableFilterItem } from 'components/data-table-filters/data-table-filters';
15 import { ContainerRequestState } from 'models/container-request';
16 import { SortDirection } from 'components/data-table/data-column';
17 import { ResourceKind, Resource } from 'models/resource';
18 import {
19     ResourceName,
20     ProcessStatus as ResourceStatus,
21     ResourceType,
22     ResourceOwnerWithName,
23     ResourcePortableDataHash,
24     ResourceFileSize,
25     ResourceFileCount,
26     ResourceUUID,
27     ResourceContainerUuid,
28     ContainerRunTime,
29     ResourceOutputUuid,
30     ResourceLogUuid,
31     ResourceParentProcess,
32     ResourceModifiedByUserUuid,
33     ResourceVersion,
34     ResourceCreatedAtDate,
35     ResourceLastModifiedDate,
36     ResourceTrashDate,
37     ResourceDeleteDate,
38 } from 'views-components/data-explorer/renderers';
39 import { ProjectIcon } from 'components/icon/icon';
40 import {
41     ResourcesState,
42     getResource
43 } from 'store/resources/resources';
44 import { loadDetailsPanel } from 'store/details-panel/details-panel-action';
45 import {
46     openContextMenu,
47     resourceUuidToContextMenuKind
48 } from 'store/context-menu/context-menu-actions';
49 import { navigateTo } from 'store/navigation/navigation-action';
50 import { getProperty } from 'store/properties/properties';
51 import { PROJECT_PANEL_CURRENT_UUID } from 'store/project-panel/project-panel-action';
52 import { ArvadosTheme } from "common/custom-theme";
53 import { createTree } from 'models/tree';
54 import {
55     getInitialResourceTypeFilters,
56     getInitialProcessStatusFilters
57 } from 'store/resource-type-filters/resource-type-filters';
58 import { GroupContentsResource } from 'services/groups-service/groups-service';
59 import { GroupClass, GroupResource } from 'models/group';
60 import { CollectionResource } from 'models/collection';
61 import { resourceIsFrozen } from 'common/frozen-resources';
62
63 type CssRules = 'root' | "button";
64
65 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
66     root: {
67         width: '100%',
68     },
69     button: {
70         marginLeft: theme.spacing.unit
71     },
72 });
73
74 export enum ProjectPanelColumnNames {
75     NAME = "Name",
76     STATUS = "Status",
77     TYPE = "Type",
78     OWNER = "Owner",
79     PORTABLE_DATA_HASH = "Portable Data Hash",
80     FILE_SIZE = "File Size",
81     FILE_COUNT = "File Count",
82     UUID = "UUID",
83     CONTAINER_UUID = "Container UUID",
84     RUNTIME = "Runtime",
85     OUTPUT_UUID = "Output UUID",
86     LOG_UUID = "Log UUID",
87     PARENT_PROCESS = 'Parent Process UUID',
88     MODIFIED_BY_USER_UUID = 'Modified by User UUID',
89     VERSION = "Version",
90     CREATED_AT = "Date Created",
91     LAST_MODIFIED = "Last Modified",
92     TRASH_AT = "Trash at",
93     DELETE_AT = "Delete at",
94 }
95
96 export interface ProjectPanelFilter extends DataTableFilterItem {
97     type: ResourceKind | ContainerRequestState;
98 }
99
100 export const projectPanelColumns: DataColumns<string> = [
101     {
102         name: ProjectPanelColumnNames.NAME,
103         selected: true,
104         configurable: true,
105         sortDirection: SortDirection.NONE,
106         filters: createTree(),
107         render: uuid => <ResourceName uuid={uuid} />
108     },
109     {
110         name: ProjectPanelColumnNames.STATUS,
111         selected: true,
112         configurable: true,
113         mutuallyExclusiveFilters: true,
114         filters: getInitialProcessStatusFilters(),
115         render: uuid => <ResourceStatus uuid={uuid} />,
116     },
117     {
118         name: ProjectPanelColumnNames.TYPE,
119         selected: true,
120         configurable: true,
121         filters: getInitialResourceTypeFilters(),
122         render: uuid => <ResourceType uuid={uuid} />
123     },
124     {
125         name: ProjectPanelColumnNames.OWNER,
126         selected: false,
127         configurable: true,
128         filters: createTree(),
129         render: uuid => <ResourceOwnerWithName uuid={uuid} />
130     },
131     {
132         name: ProjectPanelColumnNames.PORTABLE_DATA_HASH,
133         selected: false,
134         configurable: true,
135         filters: createTree(),
136         render: uuid => <ResourcePortableDataHash uuid={uuid} />
137     },
138     {
139         name: ProjectPanelColumnNames.FILE_SIZE,
140         selected: true,
141         configurable: true,
142         filters: createTree(),
143         render: uuid => <ResourceFileSize uuid={uuid} />
144     },
145     {
146         name: ProjectPanelColumnNames.FILE_COUNT,
147         selected: false,
148         configurable: true,
149         filters: createTree(),
150         render: uuid => <ResourceFileCount uuid={uuid} />
151     },
152     {
153         name: ProjectPanelColumnNames.UUID,
154         selected: false,
155         configurable: true,
156         filters: createTree(),
157         render: uuid => <ResourceUUID uuid={uuid} />
158     },
159     {
160         name: ProjectPanelColumnNames.CONTAINER_UUID,
161         selected: false,
162         configurable: true,
163         filters: createTree(),
164         render: uuid => <ResourceContainerUuid uuid={uuid} />
165     },
166     {
167         name: ProjectPanelColumnNames.RUNTIME,
168         selected: false,
169         configurable: true,
170         filters: createTree(),
171         render: uuid => <ContainerRunTime uuid={uuid} />
172     },
173     {
174         name: ProjectPanelColumnNames.OUTPUT_UUID,
175         selected: false,
176         configurable: true,
177         filters: createTree(),
178         render: uuid => <ResourceOutputUuid uuid={uuid} />
179     },
180     {
181         name: ProjectPanelColumnNames.LOG_UUID,
182         selected: false,
183         configurable: true,
184         filters: createTree(),
185         render: uuid => <ResourceLogUuid uuid={uuid} />
186     },
187     {
188         name: ProjectPanelColumnNames.PARENT_PROCESS,
189         selected: false,
190         configurable: true,
191         filters: createTree(),
192         render: uuid => <ResourceParentProcess uuid={uuid} />
193     },
194     {
195         name: ProjectPanelColumnNames.MODIFIED_BY_USER_UUID,
196         selected: false,
197         configurable: true,
198         filters: createTree(),
199         render: uuid => <ResourceModifiedByUserUuid uuid={uuid} />
200     },
201     {
202         name: ProjectPanelColumnNames.VERSION,
203         selected: false,
204         configurable: true,
205         filters: createTree(),
206         render: uuid => <ResourceVersion uuid={uuid} />
207     },
208     {
209         name: ProjectPanelColumnNames.CREATED_AT,
210         selected: false,
211         configurable: true,
212         sortDirection: SortDirection.DESC,
213         filters: createTree(),
214         render: uuid => <ResourceCreatedAtDate uuid={uuid} />
215     },
216     {
217         name: ProjectPanelColumnNames.LAST_MODIFIED,
218         selected: true,
219         configurable: true,
220         sortDirection: SortDirection.DESC,
221         filters: createTree(),
222         render: uuid => <ResourceLastModifiedDate uuid={uuid} />
223     },
224     {
225         name: ProjectPanelColumnNames.TRASH_AT,
226         selected: false,
227         configurable: true,
228         sortDirection: SortDirection.DESC,
229         filters: createTree(),
230         render: uuid => <ResourceTrashDate uuid={uuid} />
231     },
232     {
233         name: ProjectPanelColumnNames.DELETE_AT,
234         selected: false,
235         configurable: true,
236         sortDirection: SortDirection.DESC,
237         filters: createTree(),
238         render: uuid => <ResourceDeleteDate uuid={uuid} />
239     },
240
241 ];
242
243 export const PROJECT_PANEL_ID = "projectPanel";
244
245 const DEFAULT_VIEW_MESSAGES = [
246     'Your project is empty.',
247     'Please create a project or create a collection and upload a data.',
248 ];
249
250 interface ProjectPanelDataProps {
251     currentItemId: string;
252     resources: ResourcesState;
253     isAdmin: boolean;
254     userUuid: string;
255     dataExplorerItems: any;
256 }
257
258 type ProjectPanelProps = ProjectPanelDataProps & DispatchProp
259     & WithStyles<CssRules> & RouteComponentProps<{ id: string }>;
260
261
262 export const ProjectPanel = withStyles(styles)(
263     connect((state: RootState) => ({
264         currentItemId: getProperty(PROJECT_PANEL_CURRENT_UUID)(state.properties),
265         resources: state.resources,
266         userUuid: state.auth.user!.uuid
267     }))(
268         class extends React.Component<ProjectPanelProps> {
269             render() {
270                 const { classes } = this.props;
271
272                 return <div data-cy='project-panel' className={classes.root}>
273                     <DataExplorer
274                         id={PROJECT_PANEL_ID}
275                         onRowClick={this.handleRowClick}
276                         onRowDoubleClick={this.handleRowDoubleClick}
277                         onContextMenu={this.handleContextMenu}
278                         contextMenuColumn={true}
279                         defaultViewIcon={ProjectIcon}
280                         defaultViewMessages={DEFAULT_VIEW_MESSAGES}
281                     />
282                 </div>;
283             }
284
285             isCurrentItemChild = (resource: Resource) => {
286                 return resource.ownerUuid === this.props.currentItemId;
287             }
288
289             handleContextMenu = (event: React.MouseEvent<HTMLElement>, resourceUuid: string) => {
290                 const { resources, isAdmin } = this.props;
291                 const resource = getResource<GroupContentsResource>(resourceUuid)(resources);
292                 // When viewing the contents of a filter group, all contents should be treated as read only.
293                 let readonly = false;
294                 const project = getResource<GroupResource>(this.props.currentItemId)(resources);
295                 if (project && project.groupClass === GroupClass.FILTER) {
296                     readonly = true;
297                 }
298
299                 const menuKind = this.props.dispatch<any>(resourceUuidToContextMenuKind(resourceUuid, readonly));
300                 if (menuKind && resource) {
301                     this.props.dispatch<any>(openContextMenu(event, {
302                         name: resource.name,
303                         uuid: resource.uuid,
304                         ownerUuid: resource.ownerUuid,
305                         isTrashed: ('isTrashed' in resource) ? resource.isTrashed : false,
306                         kind: resource.kind,
307                         menuKind,
308                         isAdmin,
309                         isFrozen: resourceIsFrozen(resource, resources),
310                         description: resource.description,
311                         storageClassesDesired: (resource as CollectionResource).storageClassesDesired,
312                         properties: ('properties' in resource) ? resource.properties : {},
313                     }));
314                 }
315                 this.props.dispatch<any>(loadDetailsPanel(resourceUuid));
316             }
317
318             handleRowDoubleClick = (uuid: string) => {
319                 this.props.dispatch<any>(navigateTo(uuid));
320             }
321
322             handleRowClick = (uuid: string) => {
323                 this.props.dispatch<any>(loadDetailsPanel(uuid));
324             }
325
326         }
327     )
328 );