21224: fixed project panel to viewport height Arvados-DCO-1.1-Signed-off-by: Lisa...
[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 { NotFoundView } from 'views/not-found-panel/not-found-panel';
55 import { deselectAllOthers, toggleOne } from 'store/multiselect/multiselect-actions';
56 import { ProjectDetailsCard } from 'views-components/project-details-card/project-details-card';
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 export const PROJECT_PANEL_ID = 'projectPanel';
240
241 const DEFAULT_VIEW_MESSAGES = ['Your project is empty.', 'Please create a project or create a collection and upload a data.'];
242
243 interface ProjectPanelDataProps {
244     currentItemId: string;
245     resources: ResourcesState;
246     project: GroupResource;
247     isAdmin: boolean;
248     userUuid: string;
249     dataExplorerItems: any;
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             render() {
269                 const { classes } = this.props;
270
271                 return this.props.project ?
272                     <div data-cy='project-panel' className={classes.root}>
273                         <ProjectDetailsCard />
274                         <DataExplorer
275                             id={PROJECT_PANEL_ID}
276                             onRowClick={this.handleRowClick}
277                             onRowDoubleClick={this.handleRowDoubleClick}
278                             onContextMenu={this.handleContextMenu}
279                             contextMenuColumn={true}
280                             defaultViewIcon={ProjectIcon}
281                             defaultViewMessages={DEFAULT_VIEW_MESSAGES}
282                         />
283                     </div>
284                     :
285                     <NotFoundView
286                         icon={ProjectIcon}
287                         messages={["Project not found"]}
288                     />
289             }
290
291             isCurrentItemChild = (resource: Resource) => {
292                 return resource.ownerUuid === this.props.currentItemId;
293             };
294
295             handleContextMenu = (event: React.MouseEvent<HTMLElement>, resourceUuid: string) => {
296                 const { resources, isAdmin } = this.props;
297                 const resource = getResource<GroupContentsResource>(resourceUuid)(resources);
298                 // When viewing the contents of a filter group, all contents should be treated as read only.
299                 let readonly = false;
300                 const project = getResource<GroupResource>(this.props.currentItemId)(resources);
301                 if (project && project.groupClass === GroupClass.FILTER) {
302                     readonly = true;
303                 }
304
305                 const menuKind = this.props.dispatch<any>(resourceUuidToContextMenuKind(resourceUuid, readonly));
306                 if (menuKind && resource) {
307                     this.props.dispatch<any>(
308                         openContextMenu(event, {
309                             name: resource.name,
310                             uuid: resource.uuid,
311                             ownerUuid: resource.ownerUuid,
312                             isTrashed: 'isTrashed' in resource ? resource.isTrashed : false,
313                             kind: resource.kind,
314                             menuKind,
315                             isAdmin,
316                             isFrozen: resourceIsFrozen(resource, resources),
317                             description: resource.description,
318                             storageClassesDesired: (resource as CollectionResource).storageClassesDesired,
319                             properties: 'properties' in resource ? resource.properties : {},
320                         })
321                     );
322                 }
323                 this.props.dispatch<any>(loadDetailsPanel(resourceUuid));
324             };
325
326             handleRowDoubleClick = (uuid: string) => {
327                 this.props.dispatch<any>(navigateTo(uuid));
328             };
329
330             handleRowClick = (uuid: string) => {
331                 this.props.dispatch<any>(toggleOne(uuid))
332                 this.props.dispatch<any>(deselectAllOthers(uuid))
333                 this.props.dispatch<any>(loadDetailsPanel(uuid));
334             };
335         }
336     )
337 );