[ARVADOS-WORKBENCH2] created: 1.1.4-640-gefe0283

Git user git at public.curoverse.com
Tue Aug 21 04:17:53 EDT 2018


        at  efe0283919eb18e60ad876eaf6edef03c6cf04b3 (commit)


commit efe0283919eb18e60ad876eaf6edef03c6cf04b3
Author: Daniel Kos <daniel.kos at contractors.roche.com>
Date:   Tue Aug 21 10:17:44 2018 +0200

    Add trash view
    
    Feature #13828
    
    Arvados-DCO-1.1-Signed-off-by: Daniel Kos <daniel.kos at contractors.roche.com>

diff --git a/src/common/formatters.ts b/src/common/formatters.ts
index 49e0690..b1baee7 100644
--- a/src/common/formatters.ts
+++ b/src/common/formatters.ts
@@ -2,10 +2,13 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-export const formatDate = (isoDate: string) => {
-    const date = new Date(isoDate);
-    const text = date.toLocaleString();
-    return text === 'Invalid Date' ? "" : text;
+export const formatDate = (isoDate?: string) => {
+    if (isoDate) {
+        const date = new Date(isoDate);
+        const text = date.toLocaleString();
+        return text === 'Invalid Date' ? "" : text;
+    }
+    return "";
 };
 
 export const formatFileSize = (size?: number) => {
diff --git a/src/components/data-table/data-table.tsx b/src/components/data-table/data-table.tsx
index 34f8168..5a6f9e5 100644
--- a/src/components/data-table/data-table.tsx
+++ b/src/components/data-table/data-table.tsx
@@ -63,7 +63,7 @@ export const DataTable = withStyles(styles)(
             return <TableCell key={key || index} style={{ width: column.width, minWidth: column.width }}>
                 {renderHeader ?
                     renderHeader() :
-                    filters
+                    filters.length > 0
                         ? <DataTableFilters
                             name={`${name} filters`}
                             onChange={filters =>
diff --git a/src/models/collection.ts b/src/models/collection.ts
index 0e96f7f..5215998 100644
--- a/src/models/collection.ts
+++ b/src/models/collection.ts
@@ -2,9 +2,9 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { Resource, ResourceKind } from "./resource";
+import { ResourceKind, TrashResource } from "./resource";
 
-export interface CollectionResource extends Resource {
+export interface CollectionResource extends TrashResource {
     kind: ResourceKind.COLLECTION;
     name: string;
     description: string;
@@ -14,11 +14,8 @@ export interface CollectionResource extends Resource {
     replicationDesired: number;
     replicationConfirmed: number;
     replicationConfirmedAt: string;
-    trashAt: string;
-    deleteAt: string;
-    isTrashed: boolean;
 }
 
 export const getCollectionUrl = (uuid: string) => {
     return `/collections/${uuid}`;
-};
\ No newline at end of file
+};
diff --git a/src/models/container-request.ts b/src/models/container-request.ts
deleted file mode 100644
index d1bcc36..0000000
--- a/src/models/container-request.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import { Resource, ResourceKind } from "./resource";
-
-export enum ContainerRequestState {
-    UNCOMMITTED = "Uncommitted",
-    COMMITTED = "Committed",
-    FINAL = "Final"
-}
-
-export interface ContainerRequestResource extends Resource {
-    kind: ResourceKind.CONTAINER_REQUEST;
-    name: string;
-    description: string;
-    properties: any;
-    state: ContainerRequestState;
-    requestingContainerUuid: string;
-    containerUuid: string;
-    containerCountMax: number;
-    mounts: any;
-    runtimeConstraints: any;
-    schedulingParameters: any;
-    containerImage: string;
-    environment: any;
-    cwd: string;
-    command: string[];
-    outputPath: string;
-    outputName: string;
-    outputTtl: number;
-    priority: number;
-    expiresAt: string;
-    useExisting: boolean;
-    logUuid: string;
-    outputUuid: string;
-    filters: string;
-}
diff --git a/src/models/group.ts b/src/models/group.ts
index 5e8d7a1..5319250 100644
--- a/src/models/group.ts
+++ b/src/models/group.ts
@@ -2,20 +2,17 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { Resource, ResourceKind } from "./resource";
+import { ResourceKind, TrashResource } from "./resource";
 
-export interface GroupResource extends Resource {
+export interface GroupResource extends TrashResource {
     kind: ResourceKind.GROUP;
     name: string;
     groupClass: GroupClass | null;
     description: string;
     properties: string;
     writeableBy: string[];
-    trashAt: string;
-    deleteAt: string;
-    isTrashed: boolean;
 }
 
 export enum GroupClass {
     PROJECT = "project"
-}
\ No newline at end of file
+}
diff --git a/src/models/process.ts b/src/models/process.ts
index 1e04cb1..bcfbd3a 100644
--- a/src/models/process.ts
+++ b/src/models/process.ts
@@ -2,6 +2,37 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { ContainerRequestResource } from "./container-request";
+import { Resource, ResourceKind } from "./resource";
 
-export type ProcessResource = ContainerRequestResource;
+export enum ProcessState {
+    UNCOMMITTED = "Uncommitted",
+    COMMITTED = "Committed",
+    FINAL = "Final"
+}
+
+export interface ProcessResource extends Resource {
+    kind: ResourceKind.PROCESS;
+    name: string;
+    description: string;
+    properties: any;
+    state: ProcessState;
+    requestingContainerUuid: string;
+    containerUuid: string;
+    containerCountMax: number;
+    mounts: any;
+    runtimeConstraints: any;
+    schedulingParameters: any;
+    containerImage: string;
+    environment: any;
+    cwd: string;
+    command: string[];
+    outputPath: string;
+    outputName: string;
+    outputTtl: number;
+    priority: number;
+    expiresAt: string;
+    useExisting: boolean;
+    logUuid: string;
+    outputUuid: string;
+    filters: string;
+}
diff --git a/src/models/project.ts b/src/models/project.ts
index b919450..8e101ce 100644
--- a/src/models/project.ts
+++ b/src/models/project.ts
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { GroupResource, GroupClass } from "./group";
+import { GroupClass, GroupResource } from "./group";
 
 export interface ProjectResource extends GroupResource {
     groupClass: GroupClass.PROJECT;
diff --git a/src/models/resource.ts b/src/models/resource.ts
index 6a76b07..ab487da 100644
--- a/src/models/resource.ts
+++ b/src/models/resource.ts
@@ -14,9 +14,14 @@ export interface Resource {
     etag: string;
 }
 
+export interface TrashResource extends Resource {
+    trashAt: string;
+    deleteAt: string;
+    isTrashed: boolean;
+}
+
 export enum ResourceKind {
     COLLECTION = "arvados#collection",
-    CONTAINER_REQUEST = "arvados#containerRequest",
     GROUP = "arvados#group",
     PROCESS = "arvados#containerRequest",
     PROJECT = "arvados#group",
diff --git a/src/models/test-utils.ts b/src/models/test-utils.ts
index 6723430..49eea60 100644
--- a/src/models/test-utils.ts
+++ b/src/models/test-utils.ts
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { GroupResource, GroupClass } from "./group";
+import { GroupClass, GroupResource } from "./group";
 import { Resource, ResourceKind } from "./resource";
 import { ProjectResource } from "./project";
 
diff --git a/src/services/groups-service/groups-service.ts b/src/services/groups-service/groups-service.ts
index 822c810..39cc74a 100644
--- a/src/services/groups-service/groups-service.ts
+++ b/src/services/groups-service/groups-service.ts
@@ -16,6 +16,7 @@ export interface ContentsArguments {
     order?: string;
     filters?: string;
     recursive?: boolean;
+    includeTrash?: boolean;
 }
 
 export type GroupContentsResource =
diff --git a/src/services/services.ts b/src/services/services.ts
index 61dd399..9199755 100644
--- a/src/services/services.ts
+++ b/src/services/services.ts
@@ -12,8 +12,9 @@ import { CollectionService } from "./collection-service/collection-service";
 import { TagService } from "./tag-service/tag-service";
 import { CollectionFilesService } from "./collection-files-service/collection-files-service";
 import { KeepService } from "./keep-service/keep-service";
-import { WebDAV } from "../common/webdav";
-import { Config } from "../common/config";
+import { WebDAV } from "~/common/webdav";
+import { Config } from "~/common/config";
+import { TrashService } from "~/services/trash-service/trash-service";
 
 export type ServiceRepository = ReturnType<typeof createServices>;
 
@@ -30,6 +31,7 @@ export const createServices = (config: Config) => {
     const projectService = new ProjectService(apiClient);
     const linkService = new LinkService(apiClient);
     const favoriteService = new FavoriteService(linkService, groupsService);
+    const trashService = new TrashService(apiClient);
     const collectionService = new CollectionService(apiClient, keepService, webdavClient, authService);
     const tagService = new TagService(linkService);
     const collectionFilesService = new CollectionFilesService(collectionService);
@@ -43,6 +45,7 @@ export const createServices = (config: Config) => {
         projectService,
         linkService,
         favoriteService,
+        trashService,
         collectionService,
         tagService,
         collectionFilesService
diff --git a/src/services/trash-service/trash-service.test.ts b/src/services/trash-service/trash-service.test.ts
new file mode 100644
index 0000000..f22d066
--- /dev/null
+++ b/src/services/trash-service/trash-service.test.ts
@@ -0,0 +1,17 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { GroupsService } from "../groups-service/groups-service";
+import { TrashService } from "./trash-service";
+import { mockResourceService } from "~/common/api/common-resource-service.test";
+
+describe("TrashService", () => {
+
+    let groupService: GroupsService;
+
+    beforeEach(() => {
+        groupService = mockResourceService(GroupsService);
+    });
+
+});
diff --git a/src/services/trash-service/trash-service.ts b/src/services/trash-service/trash-service.ts
new file mode 100644
index 0000000..fc02d2f
--- /dev/null
+++ b/src/services/trash-service/trash-service.ts
@@ -0,0 +1,12 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { GroupsService } from "../groups-service/groups-service";
+import { AxiosInstance } from "axios";
+
+export class TrashService extends GroupsService {
+    constructor(serverApi: AxiosInstance) {
+        super(serverApi);
+    }
+}
diff --git a/src/store/side-panel/side-panel-reducer.ts b/src/store/side-panel/side-panel-reducer.ts
index db1cbe5..b68ce7a 100644
--- a/src/store/side-panel/side-panel-reducer.ts
+++ b/src/store/side-panel/side-panel-reducer.ts
@@ -10,9 +10,11 @@ import { push } from "react-router-redux";
 import { favoritePanelActions } from "../favorite-panel/favorite-panel-action";
 import { projectPanelActions } from "../project-panel/project-panel-action";
 import { projectActions } from "../project/project-action";
-import { getProjectUrl } from "../../models/project";
-import { columns as projectPanelColumns } from "../../views/project-panel/project-panel";
-import { columns as favoritePanelColumns } from "../../views/favorite-panel/favorite-panel";
+import { getProjectUrl } from "~/models/project";
+import { columns as projectPanelColumns } from "~/views/project-panel/project-panel";
+import { columns as favoritePanelColumns } from "~/views/favorite-panel/favorite-panel";
+import { columns as trashPanelColumns } from "~/views/trash-panel/trash-panel";
+import { trashPanelActions } from "~/store/trash-panel/trash-panel-action";
 
 export type SidePanelState = SidePanelItem[];
 
@@ -102,6 +104,9 @@ export const sidePanelItems = [
         active: false,
         activeAction: (dispatch: Dispatch) => {
             dispatch(push("/trash"));
+            dispatch(trashPanelActions.SET_COLUMNS({ columns: trashPanelColumns }));
+            dispatch(trashPanelActions.RESET_PAGINATION());
+            dispatch(trashPanelActions.REQUEST_ITEMS());
         }
     }
 ];
diff --git a/src/store/store.ts b/src/store/store.ts
index a4bf9d6..febaf93 100644
--- a/src/store/store.ts
+++ b/src/store/store.ts
@@ -29,6 +29,8 @@ import { CollectionsState, collectionsReducer } from './collections/collections-
 import { ServiceRepository } from "~/services/services";
 import { treePickerReducer } from './tree-picker/tree-picker-reducer';
 import { TreePicker } from './tree-picker/tree-picker';
+import { TrashPanelMiddlewareService } from "~/store/trash-panel/trash-panel-middleware-service";
+import { TRASH_PANEL_ID } from "~/store/trash-panel/trash-panel-action";
 
 const composeEnhancers =
     (process.env.NODE_ENV === 'development' &&
@@ -79,12 +81,16 @@ export function configureStore(history: History, services: ServiceRepository): R
     const favoritePanelMiddleware = dataExplorerMiddleware(
         new FavoritePanelMiddlewareService(services, FAVORITE_PANEL_ID)
     );
+    const trashPanelMiddleware = dataExplorerMiddleware(
+        new TrashPanelMiddlewareService(services, TRASH_PANEL_ID)
+    );
 
     const middlewares: Middleware[] = [
         routerMiddleware(history),
         thunkMiddleware.withExtraArgument(services),
         projectPanelMiddleware,
-        favoritePanelMiddleware
+        favoritePanelMiddleware,
+        trashPanelMiddleware
     ];
     const enhancer = composeEnhancers(applyMiddleware(...middlewares));
     return createStore(rootReducer, enhancer);
diff --git a/src/store/trash-panel/trash-panel-action.ts b/src/store/trash-panel/trash-panel-action.ts
new file mode 100644
index 0000000..84d5602
--- /dev/null
+++ b/src/store/trash-panel/trash-panel-action.ts
@@ -0,0 +1,8 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { bindDataExplorerActions } from "../data-explorer/data-explorer-action";
+
+export const TRASH_PANEL_ID = "trashPanel";
+export const trashPanelActions = bindDataExplorerActions(TRASH_PANEL_ID);
diff --git a/src/store/trash-panel/trash-panel-middleware-service.ts b/src/store/trash-panel/trash-panel-middleware-service.ts
new file mode 100644
index 0000000..2d1dbf7
--- /dev/null
+++ b/src/store/trash-panel/trash-panel-middleware-service.ts
@@ -0,0 +1,80 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { DataExplorerMiddlewareService } from "../data-explorer/data-explorer-middleware-service";
+import { RootState } from "../store";
+import { DataColumns } from "~/components/data-table/data-table";
+import { ServiceRepository } from "~/services/services";
+import { SortDirection } from "~/components/data-table/data-column";
+import { FilterBuilder } from "~/common/api/filter-builder";
+import { checkPresenceInFavorites } from "../favorites/favorites-actions";
+import { trashPanelActions } from "./trash-panel-action";
+import { Dispatch, MiddlewareAPI } from "redux";
+import { OrderBuilder, OrderDirection } from "~/common/api/order-builder";
+import { GroupContentsResourcePrefix } from "~/services/groups-service/groups-service";
+import { resourceToDataItem, TrashPanelItem } from "~/views/trash-panel/trash-panel-item";
+import { TrashPanelColumnNames, TrashPanelFilter } from "~/views/trash-panel/trash-panel";
+import { ProjectResource } from "~/models/project";
+import { ProjectPanelColumnNames } from "~/views/project-panel/project-panel";
+
+export class TrashPanelMiddlewareService extends DataExplorerMiddlewareService {
+    constructor(private services: ServiceRepository, id: string) {
+        super(id);
+    }
+
+    requestItems(api: MiddlewareAPI<Dispatch, RootState>) {
+        const dataExplorer = api.getState().dataExplorer[this.getId()];
+        const columns = dataExplorer.columns as DataColumns<TrashPanelItem, TrashPanelFilter>;
+        const sortColumn = dataExplorer.columns.find(c => c.sortDirection !== SortDirection.NONE);
+        const typeFilters = this.getColumnFilters(columns, TrashPanelColumnNames.TYPE);
+
+        const order = new OrderBuilder<ProjectResource>();
+
+        if (sortColumn) {
+            const sortDirection = sortColumn && sortColumn.sortDirection === SortDirection.ASC
+                ? OrderDirection.ASC
+                : OrderDirection.DESC;
+
+            const columnName = sortColumn && sortColumn.name === ProjectPanelColumnNames.NAME ? "name" : "createdAt";
+            order
+                .addOrder(sortDirection, columnName, GroupContentsResourcePrefix.COLLECTION)
+                .addOrder(sortDirection, columnName, GroupContentsResourcePrefix.PROCESS)
+                .addOrder(sortDirection, columnName, GroupContentsResourcePrefix.PROJECT);
+        }
+
+        const userUuid = this.services.authService.getUuid()!;
+
+        this.services.trashService
+            .contents(userUuid, {
+                limit: dataExplorer.rowsPerPage,
+                offset: dataExplorer.page * dataExplorer.rowsPerPage,
+                order: order.getOrder(),
+                filters: new FilterBuilder()
+                    .addIsA("uuid", typeFilters.map(f => f.type))
+                    .addILike("name", dataExplorer.searchValue, GroupContentsResourcePrefix.COLLECTION)
+                    .addILike("name", dataExplorer.searchValue, GroupContentsResourcePrefix.PROCESS)
+                    .addILike("name", dataExplorer.searchValue, GroupContentsResourcePrefix.PROJECT)
+                    .getFilters(),
+                recursive: true,
+                includeTrash: true
+            })
+            .then(response => {
+                api.dispatch(trashPanelActions.SET_ITEMS({
+                    items: response.items.map(resourceToDataItem).filter(it => it.isTrashed),
+                    itemsAvailable: response.itemsAvailable,
+                    page: Math.floor(response.offset / response.limit),
+                    rowsPerPage: response.limit
+                }));
+                api.dispatch<any>(checkPresenceInFavorites(response.items.map(item => item.uuid)));
+            })
+            .catch(() => {
+                api.dispatch(trashPanelActions.SET_ITEMS({
+                    items: [],
+                    itemsAvailable: 0,
+                    page: 0,
+                    rowsPerPage: dataExplorer.rowsPerPage
+                }));
+            });
+    }
+}
diff --git a/src/views-components/data-explorer/renderers.tsx b/src/views-components/data-explorer/renderers.tsx
index 1b07642..8246d02 100644
--- a/src/views-components/data-explorer/renderers.tsx
+++ b/src/views-components/data-explorer/renderers.tsx
@@ -42,7 +42,7 @@ export const renderIcon = (item: {kind: string}) => {
     }
 };
 
-export const renderDate = (date: string) => {
+export const renderDate = (date?: string) => {
     return <Typography noWrap>{formatDate(date)}</Typography>;
 };
 
diff --git a/src/views/favorite-panel/favorite-panel.tsx b/src/views/favorite-panel/favorite-panel.tsx
index 125ea27..49f1f4a 100644
--- a/src/views/favorite-panel/favorite-panel.tsx
+++ b/src/views/favorite-panel/favorite-panel.tsx
@@ -11,7 +11,7 @@ import { DataColumns } from '~/components/data-table/data-table';
 import { RouteComponentProps } from 'react-router';
 import { RootState } from '~/store/store';
 import { DataTableFilterItem } from '~/components/data-table-filters/data-table-filters';
-import { ContainerRequestState } from '~/models/container-request';
+import { ProcessState } from '~/models/process';
 import { SortDirection } from '~/components/data-table/data-column';
 import { ResourceKind } from '~/models/resource';
 import { resourceLabel } from '~/common/labels';
@@ -42,7 +42,7 @@ export enum FavoritePanelColumnNames {
 }
 
 export interface FavoritePanelFilter extends DataTableFilterItem {
-    type: ResourceKind | ContainerRequestState;
+    type: ResourceKind | ProcessState;
 }
 
 export const columns: DataColumns<FavoritePanelItem, FavoritePanelFilter> = [
@@ -62,19 +62,19 @@ export const columns: DataColumns<FavoritePanelItem, FavoritePanelFilter> = [
         sortDirection: SortDirection.NONE,
         filters: [
             {
-                name: ContainerRequestState.COMMITTED,
+                name: ProcessState.COMMITTED,
                 selected: true,
-                type: ContainerRequestState.COMMITTED
+                type: ProcessState.COMMITTED
             },
             {
-                name: ContainerRequestState.FINAL,
+                name: ProcessState.FINAL,
                 selected: true,
-                type: ContainerRequestState.FINAL
+                type: ProcessState.FINAL
             },
             {
-                name: ContainerRequestState.UNCOMMITTED,
+                name: ProcessState.UNCOMMITTED,
                 selected: true,
-                type: ContainerRequestState.UNCOMMITTED
+                type: ProcessState.UNCOMMITTED
             }
         ],
         render: renderStatus,
diff --git a/src/views/project-panel/project-panel.tsx b/src/views/project-panel/project-panel.tsx
index 0f958d2..f63584b 100644
--- a/src/views/project-panel/project-panel.tsx
+++ b/src/views/project-panel/project-panel.tsx
@@ -11,7 +11,7 @@ import { DataColumns } from '~/components/data-table/data-table';
 import { RouteComponentProps } from 'react-router';
 import { RootState } from '~/store/store';
 import { DataTableFilterItem } from '~/components/data-table-filters/data-table-filters';
-import { ContainerRequestState } from '~/models/container-request';
+import { ProcessState } from '~/models/process';
 import { SortDirection } from '~/components/data-table/data-column';
 import { ResourceKind } from '~/models/resource';
 import { resourceLabel } from '~/common/labels';
@@ -47,7 +47,7 @@ export enum ProjectPanelColumnNames {
 }
 
 export interface ProjectPanelFilter extends DataTableFilterItem {
-    type: ResourceKind | ContainerRequestState;
+    type: ResourceKind | ProcessState;
 }
 
 export const columns: DataColumns<ProjectPanelItem, ProjectPanelFilter> = [
@@ -67,19 +67,19 @@ export const columns: DataColumns<ProjectPanelItem, ProjectPanelFilter> = [
         sortDirection: SortDirection.NONE,
         filters: [
             {
-                name: ContainerRequestState.COMMITTED,
+                name: ProcessState.COMMITTED,
                 selected: true,
-                type: ContainerRequestState.COMMITTED
+                type: ProcessState.COMMITTED
             },
             {
-                name: ContainerRequestState.FINAL,
+                name: ProcessState.FINAL,
                 selected: true,
-                type: ContainerRequestState.FINAL
+                type: ProcessState.FINAL
             },
             {
-                name: ContainerRequestState.UNCOMMITTED,
+                name: ProcessState.UNCOMMITTED,
                 selected: true,
-                type: ContainerRequestState.UNCOMMITTED
+                type: ProcessState.UNCOMMITTED
             }
         ],
         render: renderStatus,
diff --git a/src/views/trash-panel/trash-panel-item.ts b/src/views/trash-panel/trash-panel-item.ts
new file mode 100644
index 0000000..8916458
--- /dev/null
+++ b/src/views/trash-panel/trash-panel-item.ts
@@ -0,0 +1,27 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { GroupContentsResource } from "~/services/groups-service/groups-service";
+import { TrashResource } from "~/models/resource";
+
+export interface TrashPanelItem {
+    uuid: string;
+    name: string;
+    kind: string;
+    fileSize?: number;
+    trashAt?: string;
+    deleteAt?: string;
+    isTrashed?: boolean;
+}
+
+export function resourceToDataItem(r: GroupContentsResource): TrashPanelItem {
+    return {
+        uuid: r.uuid,
+        name: r.name,
+        kind: r.kind,
+        trashAt: (r as TrashResource).trashAt,
+        deleteAt: (r as TrashResource).deleteAt,
+        isTrashed: (r as TrashResource).isTrashed
+    };
+}
diff --git a/src/views/favorite-panel/favorite-panel.tsx b/src/views/trash-panel/trash-panel.tsx
similarity index 57%
copy from src/views/favorite-panel/favorite-panel.tsx
copy to src/views/trash-panel/trash-panel.tsx
index 125ea27..c5a302e 100644
--- a/src/views/favorite-panel/favorite-panel.tsx
+++ b/src/views/trash-panel/trash-panel.tsx
@@ -3,7 +3,7 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
-import { FavoritePanelItem } from './favorite-panel-item';
+import { TrashPanelItem } from './trash-panel-item';
 import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core';
 import { DataExplorer } from "~/views-components/data-explorer/data-explorer";
 import { DispatchProp, connect } from 'react-redux';
@@ -11,14 +11,14 @@ import { DataColumns } from '~/components/data-table/data-table';
 import { RouteComponentProps } from 'react-router';
 import { RootState } from '~/store/store';
 import { DataTableFilterItem } from '~/components/data-table-filters/data-table-filters';
-import { ContainerRequestState } from '~/models/container-request';
+import { ProcessState } from '~/models/process';
 import { SortDirection } from '~/components/data-table/data-column';
 import { ResourceKind } from '~/models/resource';
 import { resourceLabel } from '~/common/labels';
 import { ArvadosTheme } from '~/common/custom-theme';
-import { renderName, renderStatus, renderType, renderOwner, renderFileSize, renderDate } from '~/views-components/data-explorer/renderers';
-import { FAVORITE_PANEL_ID } from "~/store/favorite-panel/favorite-panel-action";
-import { FavoriteIcon } from '~/components/icon/icon';
+import { renderName, renderType, renderFileSize, renderDate } from '~/views-components/data-explorer/renderers';
+import { TrashIcon } from '~/components/icon/icon';
+import { TRASH_PANEL_ID } from "~/store/trash-panel/trash-panel-action";
 
 type CssRules = "toolbar" | "button";
 
@@ -32,22 +32,21 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     },
 });
 
-export enum FavoritePanelColumnNames {
+export enum TrashPanelColumnNames {
     NAME = "Name",
-    STATUS = "Status",
     TYPE = "Type",
-    OWNER = "Owner",
     FILE_SIZE = "File size",
-    LAST_MODIFIED = "Last modified"
+    TRASHED_DATE = "Trashed date",
+    TO_BE_DELETED = "To be deleted"
 }
 
-export interface FavoritePanelFilter extends DataTableFilterItem {
-    type: ResourceKind | ContainerRequestState;
+export interface TrashPanelFilter extends DataTableFilterItem {
+    type: ResourceKind | ProcessState;
 }
 
-export const columns: DataColumns<FavoritePanelItem, FavoritePanelFilter> = [
+export const columns: DataColumns<TrashPanelItem, TrashPanelFilter> = [
     {
-        name: FavoritePanelColumnNames.NAME,
+        name: TrashPanelColumnNames.NAME,
         selected: true,
         configurable: true,
         sortDirection: SortDirection.ASC,
@@ -56,32 +55,7 @@ export const columns: DataColumns<FavoritePanelItem, FavoritePanelFilter> = [
         width: "450px"
     },
     {
-        name: "Status",
-        selected: true,
-        configurable: true,
-        sortDirection: SortDirection.NONE,
-        filters: [
-            {
-                name: ContainerRequestState.COMMITTED,
-                selected: true,
-                type: ContainerRequestState.COMMITTED
-            },
-            {
-                name: ContainerRequestState.FINAL,
-                selected: true,
-                type: ContainerRequestState.FINAL
-            },
-            {
-                name: ContainerRequestState.UNCOMMITTED,
-                selected: true,
-                type: ContainerRequestState.UNCOMMITTED
-            }
-        ],
-        render: renderStatus,
-        width: "75px"
-    },
-    {
-        name: FavoritePanelColumnNames.TYPE,
+        name: TrashPanelColumnNames.TYPE,
         selected: true,
         configurable: true,
         sortDirection: SortDirection.NONE,
@@ -106,66 +80,66 @@ export const columns: DataColumns<FavoritePanelItem, FavoritePanelFilter> = [
         width: "125px"
     },
     {
-        name: FavoritePanelColumnNames.OWNER,
+        name: TrashPanelColumnNames.FILE_SIZE,
         selected: true,
         configurable: true,
         sortDirection: SortDirection.NONE,
         filters: [],
-        render: item => renderOwner(item.owner),
-        width: "200px"
+        render: item => renderFileSize(item.fileSize),
+        width: "50px"
     },
     {
-        name: FavoritePanelColumnNames.FILE_SIZE,
+        name: TrashPanelColumnNames.TRASHED_DATE,
         selected: true,
         configurable: true,
         sortDirection: SortDirection.NONE,
         filters: [],
-        render: item => renderFileSize(item.fileSize),
+        render: item => renderDate(item.trashAt),
         width: "50px"
     },
     {
-        name: FavoritePanelColumnNames.LAST_MODIFIED,
+        name: TrashPanelColumnNames.TO_BE_DELETED,
         selected: true,
         configurable: true,
         sortDirection: SortDirection.NONE,
         filters: [],
-        render: item => renderDate(item.lastModified),
-        width: "150px"
-    }
+        render: item => renderDate(item.deleteAt),
+        width: "50px"
+    },
 ];
 
-interface FavoritePanelDataProps {
+interface TrashPanelDataProps {
     currentItemId: string;
 }
 
-interface FavoritePanelActionProps {
-    onItemClick: (item: FavoritePanelItem) => void;
-    onContextMenu: (event: React.MouseEvent<HTMLElement>, item: FavoritePanelItem) => void;
+interface TrashPanelActionProps {
+    onItemClick: (item: TrashPanelItem) => void;
+    onContextMenu: (event: React.MouseEvent<HTMLElement>, item: TrashPanelItem) => void;
     onDialogOpen: (ownerUuid: string) => void;
-    onItemDoubleClick: (item: FavoritePanelItem) => void;
+    onItemDoubleClick: (item: TrashPanelItem) => void;
     onItemRouteChange: (itemId: string) => void;
 }
 
-type FavoritePanelProps = FavoritePanelDataProps & FavoritePanelActionProps & DispatchProp
+type TrashPanelProps = TrashPanelDataProps & TrashPanelActionProps & DispatchProp
                         & WithStyles<CssRules> & RouteComponentProps<{ id: string }>;
 
-export const FavoritePanel = withStyles(styles)(
+export const TrashPanel = withStyles(styles)(
     connect((state: RootState) => ({ currentItemId: state.projects.currentItemId }))(
-        class extends React.Component<FavoritePanelProps> {
+        class extends React.Component<TrashPanelProps> {
             render() {
                 return <DataExplorer
-                    id={FAVORITE_PANEL_ID}
+                    id={TRASH_PANEL_ID}
                     columns={columns}
                     onRowClick={this.props.onItemClick}
                     onRowDoubleClick={this.props.onItemDoubleClick}
                     onContextMenu={this.props.onContextMenu}
-                    extractKey={(item: FavoritePanelItem) => item.uuid}
-                    defaultIcon={FavoriteIcon}
-                    defaultMessages={['Your favorites list is empty.']}/>
+                    extractKey={(item: TrashPanelItem) => item.uuid}
+                    defaultIcon={TrashIcon}
+                    defaultMessages={['Your trash list is empty.']}/>
                 ;
             }
 
-            componentWillReceiveProps({ match, currentItemId, onItemRouteChange }: FavoritePanelProps) {
+            componentWillReceiveProps({ match, currentItemId, onItemRouteChange }: TrashPanelProps) {
                 if (match.params.id !== currentItemId) {
                     onItemRouteChange(match.params.id);
                 }
diff --git a/src/views/workbench/workbench.tsx b/src/views/workbench/workbench.tsx
index a2d61d5..a3f7624 100644
--- a/src/views/workbench/workbench.tsx
+++ b/src/views/workbench/workbench.tsx
@@ -49,6 +49,8 @@ import { MultipleFilesRemoveDialog } from '~/views-components/file-remove-dialog
 import { DialogCollectionCreateWithSelectedFile } from '~/views-components/create-collection-dialog-with-selected/create-collection-dialog-with-selected';
 import { COLLECTION_CREATE_DIALOG } from '~/views-components/dialog-create/dialog-collection-create';
 import { PROJECT_CREATE_DIALOG } from '~/views-components/dialog-create/dialog-project-create';
+import { TrashPanel } from "~/views/trash-panel/trash-panel";
+import { trashPanelActions } from "~/store/trash-panel/trash-panel-action";
 
 const DRAWER_WITDH = 240;
 const APP_BAR_HEIGHT = 100;
@@ -232,6 +234,7 @@ export const Workbench = withStyles(styles)(
                                     <Route path='/' exact render={() => <Redirect to={`/projects/${this.props.authService.getUuid()}`}  />} />
                                     <Route path="/projects/:id" render={this.renderProjectPanel} />
                                     <Route path="/favorites" render={this.renderFavoritePanel} />
+                                    <Route path="/trash" render={this.renderTrashPanel} />
                                     <Route path="/collections/:id" render={this.renderCollectionPanel} />
                                 </Switch>
                             </div>
@@ -335,6 +338,33 @@ export const Workbench = withStyles(styles)(
                 }}
                 {...props} />
 
+            renderTrashPanel = (props: RouteComponentProps<{ id: string }>) => <TrashPanel
+                onItemRouteChange={() => this.props.dispatch(trashPanelActions.REQUEST_ITEMS())}
+                onContextMenu={(event, item) => {
+                    const kind = item.kind === ResourceKind.PROJECT ? ContextMenuKind.PROJECT : ContextMenuKind.RESOURCE;
+                    this.openContextMenu(event, {
+                        uuid: item.uuid,
+                        name: item.name,
+                        kind,
+                    });
+                }}
+                onDialogOpen={this.handleProjectCreationDialogOpen}
+                onItemClick={item => {
+                    this.props.dispatch(loadDetails(item.uuid, item.kind as ResourceKind));
+                }}
+                onItemDoubleClick={item => {
+                    switch (item.kind) {
+                        case ResourceKind.COLLECTION:
+                            this.props.dispatch(loadCollection(item.uuid));
+                            this.props.dispatch(push(getCollectionUrl(item.uuid)));
+                        default:
+                            this.props.dispatch(loadDetails(item.uuid, ResourceKind.PROJECT));
+                            this.props.dispatch(setProjectItem(item.uuid, ItemMode.ACTIVE));
+                    }
+
+                }}
+                {...props} />
+
             mainAppBarActions: MainAppBarActionProps = {
                 onBreadcrumbClick: ({ itemId }: NavBreadcrumb) => {
                     this.props.dispatch(setProjectItem(itemId, ItemMode.BOTH));

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list