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