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