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