From: Lucas Di Pentima Date: Mon, 20 Jan 2020 23:31:32 +0000 (-0300) Subject: 15012: Adds 'All Processes' panel. (WIP) X-Git-Tag: 2.0.0~9^2~12 X-Git-Url: https://git.arvados.org/arvados-workbench2.git/commitdiff_plain/06d87a1bfa835b10193b9ecf87f62f7a707d55fa 15012: Adds 'All Processes' panel. (WIP) Pending: * Column ordering is not working * Type filtering should only offer 2 options: main process & subprocess. Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima --- diff --git a/src/routes/route-change-handlers.ts b/src/routes/route-change-handlers.ts index b43e84bb..400ddc88 100644 --- a/src/routes/route-change-handlers.ts +++ b/src/routes/route-change-handlers.ts @@ -47,6 +47,7 @@ const handleLocationChange = (store: RootStore) => ({ pathname }: Location) => { const groupDetailsMatch = Routes.matchGroupDetailsRoute(pathname); const linksMatch = Routes.matchLinksRoute(pathname); const collectionsContentAddressMatch = Routes.matchCollectionsContentAddressRoute(pathname); + const allProcessesMatch = Routes.matchAllProcessesRoute(pathname); store.dispatch(dialogActions.CLOSE_ALL_DIALOGS()); store.dispatch(contextMenuActions.CLOSE_CONTEXT_MENU()); @@ -108,5 +109,7 @@ const handleLocationChange = (store: RootStore) => ({ pathname }: Location) => { store.dispatch(WorkbenchActions.loadLinks); } else if (collectionsContentAddressMatch) { store.dispatch(WorkbenchActions.loadCollectionContentAddress); + } else if (allProcessesMatch) { + store.dispatch(WorkbenchActions.loadAllProcesses()); } }; \ No newline at end of file diff --git a/src/routes/routes.ts b/src/routes/routes.ts index ced460e7..191fe11b 100644 --- a/src/routes/routes.ts +++ b/src/routes/routes.ts @@ -112,6 +112,9 @@ export const matchFavoritesRoute = (route: string) => export const matchTrashRoute = (route: string) => matchPath(route, { path: Routes.TRASH }); +export const matchAllProcessesRoute = (route: string) => + matchPath(route, { path: Routes.ALL_PROCESSES }); + export const matchProjectRoute = (route: string) => matchPath(route, { path: Routes.PROJECTS }); diff --git a/src/store/all-processes-panel/all-processes-panel-action.ts b/src/store/all-processes-panel/all-processes-panel-action.ts new file mode 100644 index 00000000..3d30eaec --- /dev/null +++ b/src/store/all-processes-panel/all-processes-panel-action.ts @@ -0,0 +1,10 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { bindDataExplorerActions } from "../data-explorer/data-explorer-action"; + +export const ALL_PROCESSES_PANEL_ID = "allProcessesPanel"; +export const allProcessesPanelActions = bindDataExplorerActions(ALL_PROCESSES_PANEL_ID); + +export const loadAllProcessesPanel = () => allProcessesPanelActions.REQUEST_ITEMS(); diff --git a/src/store/all-processes-panel/all-processes-panel-middleware-service.ts b/src/store/all-processes-panel/all-processes-panel-middleware-service.ts new file mode 100644 index 00000000..be8c9c49 --- /dev/null +++ b/src/store/all-processes-panel/all-processes-panel-middleware-service.ts @@ -0,0 +1,68 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { DataExplorerMiddlewareService } from "~/store/data-explorer/data-explorer-middleware-service"; +import { RootState } from "../store"; +import { ServiceRepository } from "~/services/services"; +import { FilterBuilder } from "~/services/api/filter-builder"; +import { allProcessesPanelActions } from "./all-processes-panel-action"; +import { Dispatch, MiddlewareAPI } from "redux"; +import { resourcesActions } from "~/store/resources/resources-actions"; +import { snackbarActions, SnackbarKind } from '~/store/snackbar/snackbar-actions'; +import { progressIndicatorActions } from '~/store/progress-indicator/progress-indicator-actions.ts'; +import { getDataExplorer } from "~/store/data-explorer/data-explorer-reducer"; +import { loadMissingProcessesInformation } from "~/store/project-panel/project-panel-middleware-service"; + +export class AllProcessesPanelMiddlewareService extends DataExplorerMiddlewareService { + constructor(private services: ServiceRepository, id: string) { + super(id); + } + + async requestItems(api: MiddlewareAPI) { + const dataExplorer = getDataExplorer(api.getState().dataExplorer, this.getId()); + if (!dataExplorer) { + api.dispatch(allProcessesPanelDataExplorerIsNotSet()); + } else { + try { + api.dispatch(progressIndicatorActions.START_WORKING(this.getId())); + const processItems: any = await this.services.containerRequestService.list({ + filters: new FilterBuilder() + .addILike("name", dataExplorer.searchValue) + .getFilters() + }); + + api.dispatch(progressIndicatorActions.PERSIST_STOP_WORKING(this.getId())); + api.dispatch(resourcesActions.SET_RESOURCES(processItems.items)); + await api.dispatch(loadMissingProcessesInformation(processItems.items)); + api.dispatch(allProcessesPanelActions.SET_ITEMS({ + items: processItems.items.map((resource: any) => resource.uuid), + itemsAvailable: processItems.itemsAvailable, + page: Math.floor(processItems.offset / processItems.limit), + rowsPerPage: processItems.limit + })); + } catch (e) { + api.dispatch(progressIndicatorActions.PERSIST_STOP_WORKING(this.getId())); + api.dispatch(allProcessesPanelActions.SET_ITEMS({ + items: [], + itemsAvailable: 0, + page: 0, + rowsPerPage: dataExplorer.rowsPerPage + })); + api.dispatch(couldNotFetchAllProcessesListing()); + } + } + } +} + +const allProcessesPanelDataExplorerIsNotSet = () => + snackbarActions.OPEN_SNACKBAR({ + message: 'All Processes panel is not ready.', + kind: SnackbarKind.ERROR + }); + +const couldNotFetchAllProcessesListing = () => + snackbarActions.OPEN_SNACKBAR({ + message: 'Could not fetch All Processes listing.', + kind: SnackbarKind.ERROR + }); diff --git a/src/store/store.ts b/src/store/store.ts index 83dca37d..030b6576 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -21,6 +21,7 @@ import { FAVORITE_PANEL_ID } from "./favorite-panel/favorite-panel-action"; import { PROJECT_PANEL_ID } from "./project-panel/project-panel-action"; import { ProjectPanelMiddlewareService } from "./project-panel/project-panel-middleware-service"; import { FavoritePanelMiddlewareService } from "./favorite-panel/favorite-panel-middleware-service"; +import { AllProcessesPanelMiddlewareService } from "./all-processes-panel/all-processes-panel-middleware-service"; import { collectionPanelReducer } from './collection-panel/collection-panel-reducer'; import { dialogReducer } from './dialog/dialog-reducer'; import { ServiceRepository } from "~/services/services"; @@ -66,6 +67,7 @@ import { COLLECTIONS_CONTENT_ADDRESS_PANEL_ID } from '~/store/collections-conten import { ownerNameReducer } from '~/store/owner-name/owner-name-reducer'; import { SubprocessMiddlewareService } from '~/store/subprocess-panel/subprocess-panel-middleware-service'; import { SUBPROCESS_PANEL_ID } from '~/store/subprocess-panel/subprocess-panel-actions'; +import { ALL_PROCESSES_PANEL_ID } from './all-processes-panel/all-processes-panel-action'; const composeEnhancers = (process.env.NODE_ENV === 'development' && @@ -86,6 +88,9 @@ export function configureStore(history: History, services: ServiceRepository): R const favoritePanelMiddleware = dataExplorerMiddleware( new FavoritePanelMiddlewareService(services, FAVORITE_PANEL_ID) ); + const allProcessessPanelMiddleware = dataExplorerMiddleware( + new AllProcessesPanelMiddlewareService(services, ALL_PROCESSES_PANEL_ID) + ); const trashPanelMiddleware = dataExplorerMiddleware( new TrashPanelMiddlewareService(services, TRASH_PANEL_ID) ); @@ -132,6 +137,7 @@ export function configureStore(history: History, services: ServiceRepository): R authMiddleware(services), projectPanelMiddleware, favoritePanelMiddleware, + allProcessessPanelMiddleware, trashPanelMiddleware, searchResultsPanelMiddleware, sharedWithMePanelMiddleware, diff --git a/src/store/workbench/workbench-actions.ts b/src/store/workbench/workbench-actions.ts index 9f50402d..dbf795b6 100644 --- a/src/store/workbench/workbench-actions.ts +++ b/src/store/workbench/workbench-actions.ts @@ -4,7 +4,7 @@ import { Dispatch } from 'redux'; import { RootState } from "~/store/store"; -import { getUserUuid } from "~/common/getuser"; +import { getUserUuid } from "~/common/getuser"; import { loadDetailsPanel } from '~/store/details-panel/details-panel-action'; import { snackbarActions, SnackbarKind } from '~/store/snackbar/snackbar-actions'; import { favoritePanelActions, loadFavoritePanel } from '~/store/favorite-panel/favorite-panel-action'; @@ -99,6 +99,8 @@ import { loadCollectionsContentAddressPanel, collectionsContentAddressActions } import { collectionContentAddressPanelColumns } from '~/views/collection-content-address-panel/collection-content-address-panel'; import { subprocessPanelActions } from '~/store/subprocess-panel/subprocess-panel-actions'; import { subprocessPanelColumns } from '~/views/subprocess-panel/subprocess-panel-root'; +import { loadAllProcessesPanel, allProcessesPanelActions } from '../all-processes-panel/all-processes-panel-action'; +import { allProcessesPanelColumns } from '~/views/all-processes-panel/all-processes-panel'; export const WORKBENCH_LOADING_SCREEN = 'workbenchLoadingScreen'; @@ -126,6 +128,7 @@ export const loadWorkbench = () => if (user) { dispatch(projectPanelActions.SET_COLUMNS({ columns: projectPanelColumns })); dispatch(favoritePanelActions.SET_COLUMNS({ columns: favoritePanelColumns })); + dispatch(allProcessesPanelActions.SET_COLUMNS({ columns: allProcessesPanelColumns })); dispatch(publicFavoritePanelActions.SET_COLUMNS({ columns: publicFavoritePanelColumns })); dispatch(trashPanelActions.SET_COLUMNS({ columns: trashPanelColumns })); dispatch(sharedWithMePanelActions.SET_COLUMNS({ columns: projectPanelColumns })); @@ -178,6 +181,15 @@ export const loadTrash = () => dispatch(setSidePanelBreadcrumbs(SidePanelTreeCategory.TRASH)); }); +export const loadAllProcesses = () => + handleFirstTimeLoad( + (dispatch: Dispatch) => { + dispatch(activateSidePanelTreeItem(SidePanelTreeCategory.ALL_PROCESSES)); + dispatch(loadAllProcessesPanel()); + dispatch(setSidePanelBreadcrumbs(SidePanelTreeCategory.ALL_PROCESSES)); + } + ); + export const loadProject = (uuid: string) => handleFirstTimeLoad( async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { diff --git a/src/views/all-processes-panel/all-processes-panel.tsx b/src/views/all-processes-panel/all-processes-panel.tsx new file mode 100644 index 00000000..4bd45973 --- /dev/null +++ b/src/views/all-processes-panel/all-processes-panel.tsx @@ -0,0 +1,155 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import * as React from 'react'; +import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core'; +import { DataExplorer } from "~/views-components/data-explorer/data-explorer"; +import { connect, DispatchProp } from 'react-redux'; +import { DataColumns } from '~/components/data-table/data-table'; +import { RouteComponentProps } from 'react-router'; +import { DataTableFilterItem } from '~/components/data-table-filters/data-table-filters'; +import { SortDirection } from '~/components/data-table/data-column'; +import { ResourceKind } from '~/models/resource'; +import { ArvadosTheme } from '~/common/custom-theme'; +import { ALL_PROCESSES_PANEL_ID } from '~/store/all-processes-panel/all-processes-panel-action'; +import { + ProcessStatus, + ResourceLastModifiedDate, + ResourceName, + ResourceOwner, + ResourceType +} from '~/views-components/data-explorer/renderers'; +import { ProcessIcon } from '~/components/icon/icon'; +import { openContextMenu, resourceKindToContextMenuKind } from '~/store/context-menu/context-menu-actions'; +import { loadDetailsPanel } from '~/store/details-panel/details-panel-action'; +import { navigateTo } from '~/store/navigation/navigation-action'; +import { ContainerRequestState } from "~/models/container-request"; +import { RootState } from '~/store/store'; +import { DataTableDefaultView } from '~/components/data-table-default-view/data-table-default-view'; +import { createTree } from '~/models/tree'; +import { getSimpleObjectTypeFilters } from '~/store/resource-type-filters/resource-type-filters'; + +type CssRules = "toolbar" | "button"; + +const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ + toolbar: { + paddingBottom: theme.spacing.unit * 3, + textAlign: "right" + }, + button: { + marginLeft: theme.spacing.unit + }, +}); + +export enum AllProcessesPanelColumnNames { + NAME = "Name", + STATUS = "Status", + TYPE = "Type", + OWNER = "Owner", + LAST_MODIFIED = "Last modified" +} + +export interface AllProcessesPanelFilter extends DataTableFilterItem { + type: ResourceKind | ContainerRequestState; +} + +export const allProcessesPanelColumns: DataColumns = [ + { + name: AllProcessesPanelColumnNames.NAME, + selected: true, + configurable: true, + sortDirection: SortDirection.NONE, + filters: createTree(), + render: uuid => + }, + { + name: AllProcessesPanelColumnNames.STATUS, + selected: true, + configurable: true, + filters: createTree(), + render: uuid => + }, + { + name: AllProcessesPanelColumnNames.TYPE, + selected: true, + configurable: true, + // TODO: Only filter by process type (main, subprocess) + filters: getSimpleObjectTypeFilters(), + render: uuid => + }, + { + name: AllProcessesPanelColumnNames.OWNER, + selected: true, + configurable: true, + filters: createTree(), + render: uuid => + }, + { + name: AllProcessesPanelColumnNames.LAST_MODIFIED, + selected: true, + configurable: true, + sortDirection: SortDirection.DESC, + filters: createTree(), + render: uuid => + } +]; + +interface AllProcessesPanelDataProps { + isAdmin: boolean; +} + +interface AllProcessesPanelActionProps { + onItemClick: (item: string) => void; + onDialogOpen: (ownerUuid: string) => void; + onItemDoubleClick: (item: string) => void; +} +const mapStateToProps = (state : RootState): AllProcessesPanelDataProps => ({ + isAdmin: state.auth.user!.isAdmin +}); + +type AllProcessesPanelProps = AllProcessesPanelDataProps & AllProcessesPanelActionProps & DispatchProp + & WithStyles & RouteComponentProps<{ id: string }>; + +export const AllProcessesPanel = withStyles(styles)( + connect(mapStateToProps)( + class extends React.Component { + handleContextMenu = (event: React.MouseEvent, resourceUuid: string) => { + const menuKind = resourceKindToContextMenuKind(resourceUuid, this.props.isAdmin); + if (menuKind) { + this.props.dispatch(openContextMenu(event, { + name: '', + uuid: resourceUuid, + ownerUuid: '', + kind: ResourceKind.NONE, + menuKind + })); + } + this.props.dispatch(loadDetailsPanel(resourceUuid)); + } + + handleRowDoubleClick = (uuid: string) => { + this.props.dispatch(navigateTo(uuid)); + } + + handleRowClick = (uuid: string) => { + this.props.dispatch(loadDetailsPanel(uuid)); + } + + render() { + return + } />; + } + } + ) +); diff --git a/src/views/workbench/workbench.tsx b/src/views/workbench/workbench.tsx index 8ab042d2..31c2a026 100644 --- a/src/views/workbench/workbench.tsx +++ b/src/views/workbench/workbench.tsx @@ -95,6 +95,7 @@ import { PublicFavoritePanel } from '~/views/public-favorites-panel/public-favor import { LinkAccountPanel } from '~/views/link-account-panel/link-account-panel'; import { FedLogin } from './fed-login'; import { CollectionsContentAddressPanel } from '~/views/collection-content-address-panel/collection-content-address-panel'; +import { AllProcessesPanel } from '../all-processes-panel/all-processes-panel'; type CssRules = 'root' | 'container' | 'splitter' | 'asidePanel' | 'contentWrapper' | 'content'; @@ -162,6 +163,7 @@ export const WorkbenchPanel = +