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