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