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