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