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