From: Pawel Kowalczyk Date: Fri, 21 Sep 2018 12:04:06 +0000 (+0200) Subject: workflow-view-middleware-service X-Git-Tag: 1.3.0~72^2~23^2~8 X-Git-Url: https://git.arvados.org/arvados-workbench2.git/commitdiff_plain/6acebd8c63b066f86aa81fce3b8937609778031f workflow-view-middleware-service Feature #13857 Arvados-DCO-1.1-Signed-off-by: Pawel Kowalczyk --- 6acebd8c63b066f86aa81fce3b8937609778031f diff --cc src/store/store.ts index 97be93d8,012b7474..16d0d055 --- a/src/store/store.ts +++ b/src/store/store.ts @@@ -73,6 -68,6 +73,7 @@@ export function configureStore(history favoritePanelMiddleware, trashPanelMiddleware, sharedWithMePanelMiddleware, ++ workflowPanelMiddleware ]; const enhancer = composeEnhancers(applyMiddleware(...middlewares)); return createStore(rootReducer, enhancer); diff --cc src/store/workbench/workbench-actions.ts index 9124c660,99f5bc70..5440fc90 --- a/src/store/workbench/workbench-actions.ts +++ b/src/store/workbench/workbench-actions.ts @@@ -39,8 -39,27 +39,29 @@@ import { loadProcessPanel } from '~/sto import { sharedWithMePanelActions } from '~/store/shared-with-me-panel/shared-with-me-panel-actions'; import { loadSharedWithMePanel } from '../shared-with-me-panel/shared-with-me-panel-actions'; import { CopyFormDialogData } from '~/store/copy-dialog/copy-dialog'; +import { loadWorkflowPanel, workflowPanelActions } from '~/store/workflow-panel/workflow-panel-actions'; +import { workflowPanelColumns } from '~/views/workflow-panel/workflow-panel'; + import { progressIndicatorActions } from '~/store/progress-indicator/progress-indicator-actions'; + import { getProgressIndicator } from '../progress-indicator/progress-indicator-reducer'; + + export const WORKBENCH_LOADING_SCREEN = 'workbenchLoadingScreen'; + + export const isWorkbenchLoading = (state: RootState) => { + const progress = getProgressIndicator(WORKBENCH_LOADING_SCREEN)(state.progressIndicator); + return progress ? progress.working : false; + }; + + const handleFirstTimeLoad = (action: any) => + async (dispatch: Dispatch, getState: () => RootState) => { + try { + await dispatch(action); + } finally { + if (isWorkbenchLoading(getState())) { + dispatch(progressIndicatorActions.STOP_WORKING(WORKBENCH_LOADING_SCREEN)); + } + } + }; + export const loadWorkbench = () => async (dispatch: Dispatch, getState: () => RootState) => { @@@ -53,7 -73,6 +75,7 @@@ dispatch(favoritePanelActions.SET_COLUMNS({ columns: favoritePanelColumns })); dispatch(trashPanelActions.SET_COLUMNS({ columns: trashPanelColumns })); dispatch(sharedWithMePanelActions.SET_COLUMNS({ columns: projectPanelColumns })); - dispatch(workflowPanelActions.SET_COLUMNS({ columns: workflowPanelColumns})); ++ dispatch(workflowPanelActions.SET_COLUMNS({ columns: workflowPanelColumns })); dispatch(initSidePanelTree()); if (router.location) { const match = matchRootRoute(router.location.pathname); @@@ -273,14 -297,8 +300,14 @@@ export const reloadProjectMatchingUuid } }; - export const loadSharedWithMe = (dispatch: Dispatch) => { - dispatch(activateSidePanelTreeItem(SidePanelTreeCategory.SHARED_WITH_ME)); + export const loadSharedWithMe = handleFirstTimeLoad(async (dispatch: Dispatch) => { dispatch(loadSharedWithMePanel()); - dispatch(setSidePanelBreadcrumbs(SidePanelTreeCategory.SHARED_WITH_ME)); - }; + await dispatch(activateSidePanelTreeItem(SidePanelTreeCategory.SHARED_WITH_ME)); + await dispatch(setSidePanelBreadcrumbs(SidePanelTreeCategory.SHARED_WITH_ME)); + }); + - export const loadWorkflow = (dispatch: Dispatch) => { ++export const loadWorkflow = handleFirstTimeLoad(async (dispatch: Dispatch) => { + dispatch(activateSidePanelTreeItem(SidePanelTreeCategory.WORKFLOWS)); - dispatch(loadWorkflowPanel()); ++ await dispatch(loadWorkflowPanel()); + dispatch(setSidePanelBreadcrumbs(SidePanelTreeCategory.WORKFLOWS)); - }; ++}); diff --cc src/store/workflow-panel/workflow-middleware-service.ts index 2ca5337e,00000000..7fa2dd4f mode 100644,000000..100644 --- a/src/store/workflow-panel/workflow-middleware-service.ts +++ b/src/store/workflow-panel/workflow-middleware-service.ts @@@ -1,75 -1,0 +1,77 @@@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { ServiceRepository } from '~/services/services'; +import { MiddlewareAPI, Dispatch } from 'redux'; +import { DataExplorerMiddlewareService, dataExplorerToListParams, listResultsToDataExplorerItemsMeta } from '~/store/data-explorer/data-explorer-middleware-service'; +import { RootState } from '~/store/store'; +import { snackbarActions, SnackbarKind } from '~/store/snackbar/snackbar-actions'; - import { DataExplorer } from '~/store/data-explorer/data-explorer-reducer'; ++import { DataExplorer, getDataExplorer } from '~/store/data-explorer/data-explorer-reducer'; +import { updateResources } from '~/store/resources/resources-actions'; +import { FilterBuilder } from '~/services/api/filter-builder'; +import { SortDirection } from '~/components/data-table/data-column'; +import { WorkflowPanelColumnNames } from '~/views/workflow-panel/workflow-panel'; +import { OrderDirection, OrderBuilder } from '~/services/api/order-builder'; +import { WorkflowResource } from '~/models/workflow'; +import { ListResults } from '~/services/common-service/common-resource-service'; +import { workflowPanelActions } from './workflow-panel-actions'; + +export class WorkflowMiddlewareService extends DataExplorerMiddlewareService { + constructor(private services: ServiceRepository, id: string) { + super(id); + } + + async requestItems(api: MiddlewareAPI) { ++ const state = api.getState(); ++ const dataExplorer = getDataExplorer(state.dataExplorer, this.getId()); + try { - const response = await this.services.workflowService; - api.dispatch(updateResources([])); - api.dispatch(setItems({ kind: '', offset: 4, limit: 4, items: [], itemsAvailable: 4 })); ++ const response = await this.services.workflowService.list({ order: getOrder(dataExplorer) }); ++ api.dispatch(updateResources(response.items)); ++ api.dispatch(setItems(response)); + } catch { + api.dispatch(couldNotFetchWorkflows()); + } + } +} + +export const getParams = (dataExplorer: DataExplorer) => ({ + ...dataExplorerToListParams(dataExplorer), + order: getOrder(dataExplorer), + filters: getFilters(dataExplorer), +}); + +export const getFilters = (dataExplorer: DataExplorer) => { + const filters = new FilterBuilder() + .addILike("name", dataExplorer.searchValue) + .getFilters(); + return `[${filters}]`; +}; + +export const getOrder = (dataExplorer: DataExplorer) => { + const sortColumn = dataExplorer.columns.find(c => c.sortDirection !== SortDirection.NONE); + const order = new OrderBuilder(); + if (sortColumn) { + const sortDirection = sortColumn && sortColumn.sortDirection === SortDirection.ASC + ? OrderDirection.ASC + : OrderDirection.DESC; + const columnName = sortColumn && sortColumn.name === WorkflowPanelColumnNames.NAME ? "name" : "modifiedAt"; + return order + .addOrder(sortDirection, columnName) + .getOrder(); + } else { + return order.getOrder(); + } +}; + +export const setItems = (listResults: ListResults) => + workflowPanelActions.SET_ITEMS({ + ...listResultsToDataExplorerItemsMeta(listResults), + items: listResults.items.map(resource => resource.uuid), + }); + +const couldNotFetchWorkflows = () => + snackbarActions.OPEN_SNACKBAR({ + message: 'Could not fetch workflows.', + kind: SnackbarKind.ERROR + }); diff --cc src/views-components/data-explorer/data-explorer.tsx index 74c3e64a,74c3e64a..17f2c77b --- a/src/views-components/data-explorer/data-explorer.tsx +++ b/src/views-components/data-explorer/data-explorer.tsx @@@ -15,7 -15,7 +15,7 @@@ import { DataColumns } from "~/componen interface Props { id: string; onRowClick: (item: any) => void; -- onContextMenu: (event: React.MouseEvent, item: any) => void; ++ onContextMenu?: (event: React.MouseEvent, item: any) => void; onRowDoubleClick: (item: any) => void; extractKey?: (item: any) => React.Key; } diff --cc src/views-components/main-content-bar/main-content-bar.tsx index 41442bba,071b986a..6fb419e3 --- a/src/views-components/main-content-bar/main-content-bar.tsx +++ b/src/views-components/main-content-bar/main-content-bar.tsx @@@ -13,31 -11,22 +13,31 @@@ import { matchWorkflowRoute } from '~/r interface MainContentBarProps { onDetailsPanelToggle: () => void; + buttonVisible: boolean; } - const isButtonVisible = ({ router }: RootState) => { -export const MainContentBar = connect(undefined, { - onDetailsPanelToggle: detailsPanelActions.TOGGLE_DETAILS_PANEL -})((props: MainContentBarProps) => - - - - - - - - - - - ++const isWorkflowPath = ({ router }: RootState) => { + const pathname = router.location ? router.location.pathname : ''; - const match = !matchWorkflowRoute(pathname); ++ const match = matchWorkflowRoute(pathname); + return !!match; +}; + +export const MainContentBar = connect((state: RootState) => ({ - buttonVisible: isButtonVisible(state) ++ buttonVisible: !isWorkflowPath(state) +}), { + onDetailsPanelToggle: detailsPanelActions.TOGGLE_DETAILS_PANEL + })((props: MainContentBarProps) => + + + + + + + {props.buttonVisible ? + + + + : null} + - - ); + ); diff --cc src/views/workbench/workbench.tsx index 54336575,78918be0..692c40b8 --- a/src/views/workbench/workbench.tsx +++ b/src/views/workbench/workbench.tsx @@@ -45,10 -38,8 +38,9 @@@ import { Grid } from '@material-ui/core import { SharedWithMePanel } from '../shared-with-me-panel/shared-with-me-panel'; import SplitterLayout from 'react-splitter-layout'; import { ProcessCommandDialog } from '~/views-components/process-command-dialog/process-command-dialog'; - import { isSystemWorking } from "~/store/progress-indicator/progress-indicator-reducer"; +import { WorkflowPanel } from '~/views/workflow-panel/workflow-panel'; - type CssRules = 'root' | 'container' | 'splitter' | 'asidePanel' | 'contentWrapper' | 'content' | 'appBar'; + type CssRules = 'root' | 'container' | 'splitter' | 'asidePanel' | 'contentWrapper' | 'content'; const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ root: { @@@ -80,114 -69,57 +70,58 @@@ } }); - interface WorkbenchDataProps { - user?: User; - currentToken?: string; - working: boolean; - } - - interface WorkbenchGeneralProps { - authService: AuthService; - buildInfo: string; - } - - type WorkbenchProps = WorkbenchDataProps & WorkbenchGeneralProps & DispatchProp & WithStyles; + type WorkbenchPanelProps = WithStyles; - interface WorkbenchState { - searchText: string; - } - - export const Workbench = withStyles(styles)( - connect( - (state: RootState) => ({ - user: state.auth.user, - currentToken: state.auth.apiToken, - working: isSystemWorking(state.progressIndicator) - }) - )( - class extends React.Component { - state = { - searchText: "", - }; - render() { - const { classes } = this.props; - return <> - - {this.props.working ? : null} - - - {this.props.user && - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - } + export const WorkbenchPanel = + withStyles(styles)(({ classes }: WorkbenchPanelProps) => + + + + + - - - - - - - - - - - - - - - - - - - - - ; - } - - onSearch = (searchText: string) => { - this.setState({ searchText }); - this.props.dispatch(push(`/search?q=${searchText}`)); - } - - toggleDetailsPanel = () => { - this.props.dispatch(detailsPanelActions.TOGGLE_DETAILS_PANEL()); - } - - } - ) - ); + + + + + + + + + + + + + ++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); diff --cc src/views/workflow-panel/workflow-description-card.tsx index 7aa26e6a,00000000..e2b0f295 mode 100644,000000..100644 --- a/src/views/workflow-panel/workflow-description-card.tsx +++ b/src/views/workflow-panel/workflow-description-card.tsx @@@ -1,35 -1,0 +1,37 @@@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import * as React from 'react'; - import { StyleRulesCallback, WithStyles, withStyles, Card, CardHeader, Typography } from '@material-ui/core'; ++import { StyleRulesCallback, WithStyles, withStyles, Card, CardHeader, Typography, CardContent } from '@material-ui/core'; +import { ArvadosTheme } from '~/common/custom-theme'; +import { WorkflowIcon } from '~/components/icon/icon'; +import { DataTableDefaultView } from '~/components/data-table-default-view/data-table-default-view'; + +export type CssRules = 'card'; + +const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ + card: { + height: '100%' + } +}); + +interface WorkflowDescriptionCardDataProps { +} + +type WorkflowDescriptionCardProps = WorkflowDescriptionCardDataProps & WithStyles; + +export const WorkflowDescriptionCard = withStyles(styles)( + ({ classes }: WorkflowDescriptionCardProps) => { + return + + Workflow description: + } /> - ++ ++ ++ + ; + }); diff --cc src/views/workflow-panel/workflow-panel.tsx index ef0b5fbb,00000000..92bbcad6 mode 100644,000000..100644 --- a/src/views/workflow-panel/workflow-panel.tsx +++ b/src/views/workflow-panel/workflow-panel.tsx @@@ -1,142 -1,0 +1,143 @@@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import * as React from 'react'; +import { DataExplorer } from "~/views-components/data-explorer/data-explorer"; +import { connect, DispatchProp } from 'react-redux'; +import { RootState } from '~/store/store'; +import { WorkflowIcon } from '~/components/icon/icon'; +import { ResourcesState, getResource } from '~/store/resources/resources'; +import { navigateTo } from "~/store/navigation/navigation-action"; +import { loadDetailsPanel } from "~/store/details-panel/details-panel-action"; +import { DataTableDefaultView } from '~/components/data-table-default-view/data-table-default-view'; +import { WORKFLOW_PANEL_ID } from '~/store/workflow-panel/workflow-panel-actions'; +import { openContextMenu } from '~/store/context-menu/context-menu-actions'; +import { GroupResource } from '~/models/group'; +import { ContextMenuKind } from '~/views-components/context-menu/context-menu'; +import { + ResourceLastModifiedDate, + ResourceName, +} from "~/views-components/data-explorer/renderers"; +import { SortDirection } from '~/components/data-table/data-column'; +import { DataColumns } from '~/components/data-table/data-table'; +import { DataTableFilterItem } from '~/components/data-table-filters/data-table-filters'; +import { Grid } from '@material-ui/core'; +import { WorkflowDescriptionCard } from './workflow-description-card'; + +export enum WorkflowPanelColumnNames { + NAME = "Name", + AUTHORISATION = "Authorisation", + LAST_MODIFIED = "Last modified", +} + +interface WorkflowPanelDataProps { + resources: ResourcesState; +} + +export enum ResourceStatus { + PUBLIC = 'public', + PRIVATE = 'private', + SHARED = 'shared' +} + +const resourceStatus = (type: string) => { + switch (type) { + case ResourceStatus.PUBLIC: + return "Public"; + case ResourceStatus.PRIVATE: + return "Private"; + case ResourceStatus.SHARED: + return "Shared"; + default: + return "Unknown"; + } +}; + +export const workflowPanelColumns: DataColumns = [ + { + name: WorkflowPanelColumnNames.NAME, + selected: true, + configurable: true, + sortDirection: SortDirection.ASC, + filters: [], + render: (uuid: string) => + }, + { + name: WorkflowPanelColumnNames.AUTHORISATION, + selected: true, + configurable: true, + sortDirection: SortDirection.NONE, + filters: [ + { + name: resourceStatus(ResourceStatus.PUBLIC), + selected: true, + }, + { + name: resourceStatus(ResourceStatus.PRIVATE), + selected: true, + }, + { + name: resourceStatus(ResourceStatus.SHARED), + selected: true, + } + ], ++ // do zmiany na ResourceAuthorisation + render: (uuid: string) => , + }, + { + name: WorkflowPanelColumnNames.LAST_MODIFIED, + selected: true, + configurable: true, + sortDirection: SortDirection.NONE, + filters: [], + render: (uuid: string) => + } +]; + +type WorkflowPanelProps = WorkflowPanelDataProps & DispatchProp; + +export const WorkflowPanel = connect((state: RootState) => ({ + resources: state.resources +}))( + class extends React.Component { + render() { - return - ++ return ++ + } /> + + + + + ; + } + + handleContextMenu = (event: React.MouseEvent, resourceUuid: string) => { + const resource = getResource(resourceUuid)(this.props.resources); + if (resource) { + this.props.dispatch(openContextMenu(event, { + name: '', + uuid: resource.uuid, + ownerUuid: resource.ownerUuid, + isTrashed: resource.isTrashed, + kind: resource.kind, + menuKind: ContextMenuKind.PROJECT, + })); + } + } + + handleRowDoubleClick = (uuid: string) => { + this.props.dispatch(navigateTo(uuid)); + } + + handleRowClick = (uuid: string) => { + this.props.dispatch(loadDetailsPanel(uuid)); + } + } +);