From: Pawel Kowalczyk Date: Fri, 28 Sep 2018 07:29:37 +0000 (+0200) Subject: Merge branch '13857-workflow-view' X-Git-Tag: 1.3.0~72^2^2~7 X-Git-Url: https://git.arvados.org/arvados-workbench2.git/commitdiff_plain/c8528155683c9cf4f11ee6d78da8d9b86a3203bf?hp=e9ddaee3dd51a6efbbfc509191ff00d552e1a169 Merge branch '13857-workflow-view' refs #13857 Arvados-DCO-1.1-Signed-off-by: Pawel Kowalczyk --- diff --git a/src/components/data-explorer/data-explorer.tsx b/src/components/data-explorer/data-explorer.tsx index 59f4dbeb..d7abde7b 100644 --- a/src/components/data-explorer/data-explorer.tsx +++ b/src/components/data-explorer/data-explorer.tsx @@ -12,7 +12,7 @@ import { DataTableFilterItem } from '../data-table-filters/data-table-filters'; import { SearchInput } from '../search-input/search-input'; import { ArvadosTheme } from "~/common/custom-theme"; -type CssRules = 'searchBox' | "toolbar"; +type CssRules = 'searchBox' | "toolbar" | "root"; const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ searchBox: { @@ -21,6 +21,9 @@ const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ toolbar: { paddingTop: theme.spacing.unit * 2 }, + root: { + height: '100%' + } }); interface DataExplorerDataProps { @@ -66,7 +69,7 @@ export const DataExplorer = withStyles(styles)( items, itemsAvailable, onRowClick, onRowDoubleClick, classes, dataTableDefaultView } = this.props; - return + return
diff --git a/src/index.tsx b/src/index.tsx index 52852847..d0154b66 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -38,6 +38,7 @@ import { addRouteChangeHandlers } from './routes/route-change-handlers'; import { setCurrentTokenDialogApiHost } from '~/store/current-token-dialog/current-token-dialog-actions'; import { processResourceActionSet } from '~/views-components/context-menu/action-sets/process-resource-action-set'; import { progressIndicatorActions } from '~/store/progress-indicator/progress-indicator-actions'; +import { setUuidPrefix } from '~/store/workflow-panel/workflow-panel-actions'; import { trashedCollectionActionSet } from '~/views-components/context-menu/action-sets/trashed-collection-action-set'; const getBuildNumber = () => "BN-" + (process.env.REACT_APP_BUILD_NUMBER || "dev"); @@ -78,6 +79,7 @@ fetchConfig() store.subscribe(initListener(history, store, services, config)); store.dispatch(initAuth()); store.dispatch(setCurrentTokenDialogApiHost(apiHost)); + store.dispatch(setUuidPrefix(config.uuidPrefix)); const TokenComponent = (props: any) => ; const MainPanelComponent = (props: any) => ; diff --git a/src/models/resource.ts b/src/models/resource.ts index 698bcf73..7e40c738 100644 --- a/src/models/resource.ts +++ b/src/models/resource.ts @@ -40,6 +40,7 @@ export enum ResourceObjectType { GROUP = 'j7d0g', LOG = '57u5n', USER = 'tpzed', + WORKFLOW = '7fd4e' } export const RESOURCE_UUID_PATTERN = '.{5}-.{5}-.{15}'; @@ -70,6 +71,8 @@ export const extractUuidKind = (uuid: string = '') => { return ResourceKind.CONTAINER; case ResourceObjectType.LOG: return ResourceKind.LOG; + case ResourceObjectType.WORKFLOW: + return ResourceKind.WORKFLOW; default: return undefined; } diff --git a/src/routes/route-change-handlers.ts b/src/routes/route-change-handlers.ts index 33e0bef7..97613147 100644 --- a/src/routes/route-change-handlers.ts +++ b/src/routes/route-change-handlers.ts @@ -4,11 +4,10 @@ import { History, Location } from 'history'; import { RootStore } from '~/store/store'; -import { matchProcessRoute, matchProcessLogRoute, matchProjectRoute, matchCollectionRoute, matchFavoritesRoute, matchTrashRoute, matchRootRoute, matchSharedWithMeRoute } from './routes'; +import { matchProcessRoute, matchProcessLogRoute, matchProjectRoute, matchCollectionRoute, matchFavoritesRoute, matchTrashRoute, matchRootRoute, matchSharedWithMeRoute, matchWorkflowRoute } from './routes'; import { loadProject, loadCollection, loadFavorites, loadTrash, loadProcess, loadProcessLog } from '~/store/workbench/workbench-actions'; import { navigateToRootProject } from '~/store/navigation/navigation-action'; -import { navigateToSharedWithMe } from '../store/navigation/navigation-action'; -import { loadSharedWithMe } from '../store/workbench/workbench-actions'; +import { loadSharedWithMe, loadWorkflow } from '~/store/workbench/workbench-actions'; export const addRouteChangeHandlers = (history: History, store: RootStore) => { const handler = handleLocationChange(store); @@ -25,6 +24,7 @@ const handleLocationChange = (store: RootStore) => ({ pathname }: Location) => { const processMatch = matchProcessRoute(pathname); const processLogMatch = matchProcessLogRoute(pathname); const sharedWithMeMatch = matchSharedWithMeRoute(pathname); + const workflowMatch = matchWorkflowRoute(pathname); if (projectMatch) { store.dispatch(loadProject(projectMatch.params.id)); @@ -42,5 +42,7 @@ const handleLocationChange = (store: RootStore) => ({ pathname }: Location) => { store.dispatch(navigateToRootProject); } else if (sharedWithMeMatch) { store.dispatch(loadSharedWithMe); + } else if (workflowMatch) { + store.dispatch(loadWorkflow); } }; diff --git a/src/routes/routes.ts b/src/routes/routes.ts index fb28bd05..34b15e11 100644 --- a/src/routes/routes.ts +++ b/src/routes/routes.ts @@ -17,6 +17,7 @@ export const Routes = { TRASH: '/trash', PROCESS_LOGS: `/process-logs/:id(${RESOURCE_UUID_PATTERN})`, SHARED_WITH_ME: '/shared-with-me', + WORKFLOWS: '/workflows' }; export const getResourceUrl = (uuid: string) => { @@ -64,3 +65,6 @@ export const matchProcessLogRoute = (route: string) => export const matchSharedWithMeRoute = (route: string) => matchPath(route, { path: Routes.SHARED_WITH_ME }); + +export const matchWorkflowRoute = (route: string) => + matchPath(route, { path: Routes.WORKFLOWS }); diff --git a/src/services/services.ts b/src/services/services.ts index 9c764b09..738b69de 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -21,6 +21,7 @@ import { ContainerRequestService } from './container-request-service/container-r import { ContainerService } from './container-service/container-service'; import { LogService } from './log-service/log-service'; import { ApiActions } from "~/services/api/api-actions"; +import { WorkflowService } from './workflow-service/workflow-service'; export type ServiceRepository = ReturnType; @@ -39,6 +40,7 @@ export const createServices = (config: Config, actions: ApiActions) => { const logService = new LogService(apiClient, actions); const projectService = new ProjectService(apiClient, actions); const userService = new UserService(apiClient, actions); + const workflowService = new WorkflowService(apiClient, actions); const ancestorsService = new AncestorService(groupsService, userService); const authService = new AuthService(apiClient, config.rootUrl, actions); @@ -64,6 +66,7 @@ export const createServices = (config: Config, actions: ApiActions) => { tagService, userService, webdavClient, + workflowService }; }; diff --git a/src/services/workflow-service/workflow-service.ts b/src/services/workflow-service/workflow-service.ts new file mode 100644 index 00000000..60f898f1 --- /dev/null +++ b/src/services/workflow-service/workflow-service.ts @@ -0,0 +1,14 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { AxiosInstance } from "axios"; +import { WorkflowResource } from '~/models/workflow'; +import { CommonResourceService } from "~/services/common-service/common-resource-service"; +import { ApiActions } from "~/services/api/api-actions"; + +export class WorkflowService extends CommonResourceService { + constructor(serverApi: AxiosInstance, actions: ApiActions) { + super(serverApi, "workflows", actions); + } +} diff --git a/src/store/navigation/navigation-action.ts b/src/store/navigation/navigation-action.ts index 943f38ce..c8a554c7 100644 --- a/src/store/navigation/navigation-action.ts +++ b/src/store/navigation/navigation-action.ts @@ -26,6 +26,8 @@ export const navigateTo = (uuid: string) => dispatch(navigateToFavorites); } else if (uuid === SidePanelTreeCategory.SHARED_WITH_ME) { dispatch(navigateToSharedWithMe); + } else if (uuid === SidePanelTreeCategory.WORKFLOWS) { + dispatch(navigateToWorkflows); } else if (uuid === SidePanelTreeCategory.TRASH) { dispatch(navigateToTrash); } @@ -35,6 +37,8 @@ export const navigateToFavorites = push(Routes.FAVORITES); export const navigateToTrash = push(Routes.TRASH); +export const navigateToWorkflows = push(Routes.WORKFLOWS); + export const navigateToProject = compose(push, getProjectUrl); export const navigateToCollection = compose(push, getCollectionUrl); diff --git a/src/store/shared-with-me-panel/shared-with-me-middleware-service.ts b/src/store/shared-with-me-panel/shared-with-me-middleware-service.ts index 1ebb13ec..c26a7a5a 100644 --- a/src/store/shared-with-me-panel/shared-with-me-middleware-service.ts +++ b/src/store/shared-with-me-panel/shared-with-me-middleware-service.ts @@ -37,7 +37,6 @@ export class SharedWithMeMiddlewareService extends DataExplorerMiddlewareService } catch (e) { api.dispatch(couldNotFetchSharedItems()); } - } } diff --git a/src/store/side-panel-tree/side-panel-tree-actions.ts b/src/store/side-panel-tree/side-panel-tree-actions.ts index 3fd2d68a..22a83dda 100644 --- a/src/store/side-panel-tree/side-panel-tree-actions.ts +++ b/src/store/side-panel-tree/side-panel-tree-actions.ts @@ -13,7 +13,6 @@ import { getTreePicker, TreePicker } from '../tree-picker/tree-picker'; import { TreeItemStatus } from "~/components/tree/tree"; import { getNodeAncestors, getNodeValue, getNodeAncestorsIds, getNode } from '~/models/tree'; import { ProjectResource } from '~/models/project'; -import { progressIndicatorActions } from '../progress-indicator/progress-indicator-actions'; import { OrderBuilder } from '../../services/api/order-builder'; export enum SidePanelTreeCategory { diff --git a/src/store/side-panel/side-panel-action.ts b/src/store/side-panel/side-panel-action.ts index 2a5fdd0c..fd08ee13 100644 --- a/src/store/side-panel/side-panel-action.ts +++ b/src/store/side-panel/side-panel-action.ts @@ -4,7 +4,7 @@ import { Dispatch } from 'redux'; import { isSidePanelTreeCategory, SidePanelTreeCategory } from '~/store/side-panel-tree/side-panel-tree-actions'; -import { navigateToFavorites, navigateTo, navigateToTrash, navigateToSharedWithMe } from '../navigation/navigation-action'; +import { navigateToFavorites, navigateTo, navigateToTrash, navigateToSharedWithMe, navigateToWorkflows } from '../navigation/navigation-action'; import { snackbarActions } from '~/store/snackbar/snackbar-actions'; export const navigateFromSidePanel = (id: string) => @@ -24,6 +24,8 @@ const getSidePanelTreeCategoryAction = (id: string) => { return navigateToTrash; case SidePanelTreeCategory.SHARED_WITH_ME: return navigateToSharedWithMe; + case SidePanelTreeCategory.WORKFLOWS: + return navigateToWorkflows; default: return sidePanelTreeCategoryNotAvailable(id); } diff --git a/src/store/store.ts b/src/store/store.ts index 012b7474..16d0d055 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -35,6 +35,8 @@ import { processPanelReducer } from '~/store/process-panel/process-panel-reducer import { SHARED_WITH_ME_PANEL_ID } from '~/store/shared-with-me-panel/shared-with-me-panel-actions'; import { SharedWithMeMiddlewareService } from './shared-with-me-panel/shared-with-me-middleware-service'; import { progressIndicatorReducer } from './progress-indicator/progress-indicator-reducer'; +import { WorkflowMiddlewareService } from './workflow-panel/workflow-middleware-service'; +import { WORKFLOW_PANEL_ID } from './workflow-panel/workflow-panel-actions'; const composeEnhancers = (process.env.NODE_ENV === 'development' && @@ -60,6 +62,9 @@ export function configureStore(history: History, services: ServiceRepository): R const sharedWithMePanelMiddleware = dataExplorerMiddleware( new SharedWithMeMiddlewareService(services, SHARED_WITH_ME_PANEL_ID) ); + const workflowPanelMiddleware = dataExplorerMiddleware( + new WorkflowMiddlewareService(services, WORKFLOW_PANEL_ID) + ); const middlewares: Middleware[] = [ routerMiddleware(history), @@ -68,6 +73,7 @@ export function configureStore(history: History, services: ServiceRepository): R favoritePanelMiddleware, trashPanelMiddleware, sharedWithMePanelMiddleware, + workflowPanelMiddleware ]; const enhancer = composeEnhancers(applyMiddleware(...middlewares)); return createStore(rootReducer, enhancer); diff --git a/src/store/workbench/workbench-actions.ts b/src/store/workbench/workbench-actions.ts index 94b4b4f5..8f034ec0 100644 --- a/src/store/workbench/workbench-actions.ts +++ b/src/store/workbench/workbench-actions.ts @@ -38,6 +38,8 @@ import { loadProcessPanel } from '~/store/process-panel/process-panel-actions'; 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-view'; import { progressIndicatorActions } from '~/store/progress-indicator/progress-indicator-actions'; import { getProgressIndicator } from '../progress-indicator/progress-indicator-reducer'; import { ResourceKind, extractUuidKind } from '~/models/resource'; @@ -76,6 +78,7 @@ export const loadWorkbench = () => 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(initSidePanelTree()); if (router.location) { const match = matchRootRoute(router.location.pathname); @@ -347,6 +350,11 @@ export const loadSharedWithMe = handleFirstTimeLoad(async (dispatch: Dispatch) = await dispatch(setSidePanelBreadcrumbs(SidePanelTreeCategory.SHARED_WITH_ME)); }); +export const loadWorkflow = handleFirstTimeLoad(async (dispatch: Dispatch) => { + dispatch(activateSidePanelTreeItem(SidePanelTreeCategory.WORKFLOWS)); + await dispatch(loadWorkflowPanel()); + dispatch(setSidePanelBreadcrumbs(SidePanelTreeCategory.WORKFLOWS)); +}); const finishLoadingProject = (project: GroupContentsResource | string) => async (dispatch: Dispatch) => { const uuid = typeof project === 'string' ? project : project.uuid; diff --git a/src/store/workflow-panel/workflow-middleware-service.ts b/src/store/workflow-panel/workflow-middleware-service.ts new file mode 100644 index 00000000..1002979a --- /dev/null +++ b/src/store/workflow-panel/workflow-middleware-service.ts @@ -0,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, 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-view'; +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.list(getParams(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 + }); \ No newline at end of file diff --git a/src/store/workflow-panel/workflow-panel-actions.ts b/src/store/workflow-panel/workflow-panel-actions.ts new file mode 100644 index 00000000..aa79347c --- /dev/null +++ b/src/store/workflow-panel/workflow-panel-actions.ts @@ -0,0 +1,26 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { Dispatch } from 'redux'; +import { RootState } from '~/store/store'; +import { ServiceRepository } from '~/services/services'; +import { bindDataExplorerActions } from '~/store/data-explorer/data-explorer-action'; +import { propertiesActions } from '~/store/properties/properties-actions'; + +export const WORKFLOW_PANEL_ID = "workflowPanel"; +const UUID_PREFIX_PROPERTY_NAME = 'uuidPrefix'; + +export const workflowPanelActions = bindDataExplorerActions(WORKFLOW_PANEL_ID); + +export const loadWorkflowPanel = () => + (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + dispatch(workflowPanelActions.REQUEST_ITEMS()); + }; + +export const setUuidPrefix = (uuidPrefix: string) => + propertiesActions.SET_PROPERTY({ key: UUID_PREFIX_PROPERTY_NAME, value: uuidPrefix }); + +export const getUuidPrefix = (state: RootState) =>{ + return state.properties.uuidPrefix; +}; \ No newline at end of file diff --git a/src/views-components/data-explorer/data-explorer.tsx b/src/views-components/data-explorer/data-explorer.tsx index 74c3e64a..17f2c77b 100644 --- a/src/views-components/data-explorer/data-explorer.tsx +++ b/src/views-components/data-explorer/data-explorer.tsx @@ -15,7 +15,7 @@ import { DataColumns } from "~/components/data-table/data-table"; 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 --git a/src/views-components/data-explorer/renderers.tsx b/src/views-components/data-explorer/renderers.tsx index b9cc63c3..12e1be78 100644 --- a/src/views-components/data-explorer/renderers.tsx +++ b/src/views-components/data-explorer/renderers.tsx @@ -3,10 +3,10 @@ // SPDX-License-Identifier: AGPL-3.0 import * as React from 'react'; -import { Grid, Typography, withStyles } from '@material-ui/core'; +import { Grid, Typography, withStyles, Tooltip, IconButton } from '@material-ui/core'; import { FavoriteStar } from '../favorite-star/favorite-star'; import { ResourceKind, TrashableResource } from '~/models/resource'; -import { ProjectIcon, CollectionIcon, ProcessIcon, DefaultIcon } from '~/components/icon/icon'; +import { ProjectIcon, CollectionIcon, ProcessIcon, DefaultIcon, WorkflowIcon, ShareIcon } from '~/components/icon/icon'; import { formatDate, formatFileSize } from '~/common/formatters'; import { resourceLabel } from '~/common/labels'; import { connect } from 'react-redux'; @@ -16,6 +16,9 @@ import { GroupContentsResource } from '~/services/groups-service/groups-service' import { getProcess, Process, getProcessStatus, getProcessStatusColor } from '~/store/processes/process'; import { ArvadosTheme } from '~/common/custom-theme'; import { compose } from 'redux'; +import { WorkflowResource } from '~/models/workflow'; +import { ResourceStatus } from '~/views/workflow-panel/workflow-panel-view'; +import { getUuidPrefix } from '~/store/workflow-panel/workflow-panel-actions'; export const renderName = (item: { name: string; uuid: string, kind: string }) => @@ -48,6 +51,8 @@ export const renderIcon = (item: { kind: string }) => { return ; case ResourceKind.PROCESS: return ; + case ResourceKind.WORKFLOW: + return ; default: return ; } @@ -57,6 +62,68 @@ export const renderDate = (date?: string) => { return {formatDate(date)}; }; +export const renderWorkflowName = (item: { name: string; uuid: string, kind: string, ownerUuid: string }) => + + + {renderIcon(item)} + + + + {item.name} + + + ; + +export const RosurceWorkflowName = connect( + (state: RootState, props: { uuid: string }) => { + const resource = getResource(props.uuid)(state.resources); + return resource || { name: '', uuid: '', kind: '', ownerUuid: '' }; + })(renderWorkflowName); + +const getPublicUuid = (uuidPrefix: string) => { + return `${uuidPrefix}-tpzed-anonymouspublic`; +}; + +// do share onClick +export const resourceShare = (uuidPrefix: string, ownerUuid?: string) => { + return + undefined}> + {ownerUuid === getPublicUuid(uuidPrefix) ? : null} + + ; +}; + +export const ResourceShare = connect( + (state: RootState, props: { uuid: string }) => { + const resource = getResource(props.uuid)(state.resources); + const uuidPrefix = getUuidPrefix(state); + return { + ownerUuid: resource ? resource.ownerUuid : '', + uuidPrefix + }; + })((props: { ownerUuid?: string, uuidPrefix: string }) => resourceShare(props.uuidPrefix, props.ownerUuid)); + +export const renderWorkflowStatus = (uuidPrefix: string, ownerUuid?: string) => { + if (ownerUuid === getPublicUuid(uuidPrefix)) { + return renderStatus(ResourceStatus.PUBLIC); + } else { + return renderStatus(ResourceStatus.PRIVATE); + } +}; + +const renderStatus = (status: string) => + {status}; + +export const ResourceWorkflowStatus = connect( + (state: RootState, props: { uuid: string }) => { + const resource = getResource(props.uuid)(state.resources); + const uuidPrefix = getUuidPrefix(state); + return { + ownerUuid: resource ? resource.ownerUuid : '', + uuidPrefix + }; + })((props: { ownerUuid?: string, uuidPrefix: string }) => renderWorkflowStatus(props.uuidPrefix, props.ownerUuid)); + export const ResourceLastModifiedDate = connect( (state: RootState, props: { uuid: string }) => { const resource = getResource(props.uuid)(state.resources); diff --git a/src/views-components/main-content-bar/main-content-bar.tsx b/src/views-components/main-content-bar/main-content-bar.tsx index 071b986a..6fb419e3 100644 --- a/src/views-components/main-content-bar/main-content-bar.tsx +++ b/src/views-components/main-content-bar/main-content-bar.tsx @@ -8,25 +8,36 @@ import { DetailsIcon } from "~/components/icon/icon"; import { Breadcrumbs } from "~/views-components/breadcrumbs/breadcrumbs"; import { detailsPanelActions } from "~/store/details-panel/details-panel-action"; import { connect } from 'react-redux'; +import { RootState } from '~/store/store'; +import { matchWorkflowRoute } from '~/routes/routes'; interface MainContentBarProps { onDetailsPanelToggle: () => void; + buttonVisible: boolean; } -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); + return !!match; +}; + +export const MainContentBar = connect((state: RootState) => ({ + buttonVisible: !isWorkflowPath(state) +}), { + onDetailsPanelToggle: detailsPanelActions.TOGGLE_DETAILS_PANEL + })((props: MainContentBarProps) => + + + + + + + {props.buttonVisible ? + + + + : null} + - - ); + ); diff --git a/src/views/workbench/workbench.tsx b/src/views/workbench/workbench.tsx index b0d14f09..776850ce 100644 --- a/src/views/workbench/workbench.tsx +++ b/src/views/workbench/workbench.tsx @@ -39,6 +39,7 @@ 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 { WorkflowPanel } from '~/views/workflow-panel/workflow-panel'; type CssRules = 'root' | 'container' | 'splitter' | 'asidePanel' | 'contentWrapper' | 'content'; @@ -94,6 +95,7 @@ export const WorkbenchPanel = + diff --git a/src/views/workflow-panel/workflow-description-card.tsx b/src/views/workflow-panel/workflow-description-card.tsx new file mode 100644 index 00000000..60e17b60 --- /dev/null +++ b/src/views/workflow-panel/workflow-description-card.tsx @@ -0,0 +1,58 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import * as React from 'react'; +import { StyleRulesCallback, WithStyles, withStyles, CardContent, Tab, Tabs, Paper } 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'; +import { WorkflowResource } from '~/models/workflow'; + +export type CssRules = 'root' | 'tab'; + +const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ + root: { + height: '100%', + }, + tab: { + minWidth: '50%' + } +}); + +interface WorkflowDetailsCardDataProps { + workflow?: WorkflowResource; +} + +type WorkflowDetailsCardProps = WorkflowDetailsCardDataProps & WithStyles; + +export const WorkflowDetailsCard = withStyles(styles)( + class extends React.Component { + state = { + value: 0, + }; + + handleChange = (event: React.MouseEvent, value: number) => { + this.setState({ value }); + } + + render() { + const { classes } = this.props; + const { value } = this.state; + return + + + + + {value === 0 && + Description + + } + {value === 1 && + Inputs + } + ; + } + }); \ No newline at end of file diff --git a/src/views/workflow-panel/workflow-panel-view.tsx b/src/views/workflow-panel/workflow-panel-view.tsx new file mode 100644 index 00000000..8a29cb7f --- /dev/null +++ b/src/views/workflow-panel/workflow-panel-view.tsx @@ -0,0 +1,120 @@ +// 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 { WorkflowIcon } from '~/components/icon/icon'; +import { DataTableDefaultView } from '~/components/data-table-default-view/data-table-default-view'; +import { WORKFLOW_PANEL_ID } from '~/store/workflow-panel/workflow-panel-actions'; +import { + ResourceLastModifiedDate, + RosurceWorkflowName, + ResourceWorkflowStatus, + ResourceShare +} 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 { WorkflowDetailsCard } from './workflow-description-card'; + +export enum WorkflowPanelColumnNames { + NAME = "Name", + AUTHORISATION = "Authorisation", + LAST_MODIFIED = "Last modified", + SHARE = 'Share' +} + +export interface WorkflowPanelFilter extends DataTableFilterItem { + type: ResourceStatus; +} + +interface WorkflowPanelDataProps { + handleRowDoubleClick: any; + handleRowClick: any; +} + +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, + filters: [ + { + name: resourceStatus(ResourceStatus.PUBLIC), + selected: true, + type: ResourceStatus.PUBLIC + }, + { + name: resourceStatus(ResourceStatus.PRIVATE), + selected: true, + type: ResourceStatus.PRIVATE + }, + { + name: resourceStatus(ResourceStatus.SHARED), + selected: true, + type: ResourceStatus.SHARED + } + ], + render: (uuid: string) => , + }, + { + name: WorkflowPanelColumnNames.LAST_MODIFIED, + selected: true, + configurable: true, + sortDirection: SortDirection.NONE, + filters: [], + render: (uuid: string) => + }, + { + name: '', + selected: true, + configurable: false, + filters: [], + render: (uuid: string) => + } +]; + +export const WorkflowPanelView = ({...props}) => { + return + + } /> + + + + + ; +}; \ No newline at end of file diff --git a/src/views/workflow-panel/workflow-panel.tsx b/src/views/workflow-panel/workflow-panel.tsx new file mode 100644 index 00000000..279097d8 --- /dev/null +++ b/src/views/workflow-panel/workflow-panel.tsx @@ -0,0 +1,24 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import * as React from "react"; +import { Dispatch } from "redux"; +import { connect } from "react-redux"; +import { navigateTo } from '~/store/navigation/navigation-action'; +import { loadDetailsPanel } from '~/store/details-panel/details-panel-action'; +import { WorkflowPanelView } from '~/views/workflow-panel/workflow-panel-view'; + +const mapDispatchToProps = (dispatch: Dispatch) => ({ + + handleRowDoubleClick: (uuid: string) => { + dispatch(navigateTo(uuid)); + }, + + handleRowClick: (uuid: string) => { + dispatch(loadDetailsPanel(uuid)); + } +}); + +export const WorkflowPanel= connect(undefined, mapDispatchToProps)( + (props) => ); \ No newline at end of file