From f1db282d059c1d0a6e264943344e09bda5d40282 Mon Sep 17 00:00:00 2001 From: Pawel Kowalczyk Date: Tue, 15 Jan 2019 09:11:20 +0100 Subject: [PATCH] re-run-workflow-and-display-workflow-inside-process-info-card Feature #14643 Arvados-DCO-1.1-Signed-off-by: Pawel Kowalczyk --- package.json | 10 ++-- src/components/code-snippet/code-snippet.tsx | 2 +- .../collection-panel-files.tsx | 8 +-- src/components/tree/tree.tsx | 2 +- src/models/workflow.ts | 19 +++++-- .../context-menu/context-menu-actions.ts | 2 + .../process-panel/process-panel-actions.ts | 2 +- src/store/processes/process-input-actions.ts | 2 +- src/store/processes/processes-actions.ts | 53 +++++++++++++++++-- .../run-process-panel-actions.ts | 4 ++ .../workflow-panel/workflow-panel-actions.ts | 18 ++++--- .../action-sets/process-action-set.ts | 11 ++-- .../process-input-dialog.tsx | 2 +- .../process-information-card.tsx | 38 ++++++------- .../process-panel/process-panel-root.tsx | 2 +- 15 files changed, 123 insertions(+), 52 deletions(-) diff --git a/package.json b/package.json index 46b3ee4f..266ade27 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "@types/react-dnd": "3.0.2", "@types/react-dropzone": "4.2.2", "@types/react-highlight-words": "0.12.0", - "@types/redux-form": "7.4.12", + "@types/redux-form": "7.4.15", "@types/reselect": "2.2.0", "@types/shell-quote": "1.6.0", "axios": "0.18.0", @@ -24,7 +24,7 @@ "js-yaml": "3.12.0", "jssha": "2.3.1", "lodash": "4.17.11", - "react": "16.5.2", + "react": "16.7.0", "react-copy-to-clipboard": "5.0.1", "react-dnd": "5.0.0", "react-dnd-html5-backend": "5.0.1", @@ -52,7 +52,7 @@ "build": "REACT_APP_BUILD_NUMBER=$BUILD_NUMBER REACT_APP_GIT_COMMIT=$GIT_COMMIT react-scripts-ts build", "build-local": "react-scripts-ts build", "test": "CI=true react-scripts-ts test --env=jsdom", - "test-local": "react-scripts-ts test --env=jsdom", + "test-local": "react-scripts-ts test --env=jsdom --watchAll=false", "eject": "react-scripts-ts eject", "lint": "tslint src/** -t verbose", "build-css": "node-sass-chokidar src/ -o src/", @@ -64,8 +64,8 @@ "@types/enzyme-adapter-react-16": "1.0.3", "@types/jest": "23.3.3", "@types/node": "10.11.2", - "@types/react": "16.4", - "@types/react-dom": "16.0.8", + "@types/react": "16.7.18", + "@types/react-dom": "16.0.11", "@types/react-redux": "6.0.9", "@types/react-router": "4.0.31", "@types/react-router-dom": "4.3.1", diff --git a/src/components/code-snippet/code-snippet.tsx b/src/components/code-snippet/code-snippet.tsx index 6cba299f..a23a5fe2 100644 --- a/src/components/code-snippet/code-snippet.tsx +++ b/src/components/code-snippet/code-snippet.tsx @@ -3,7 +3,7 @@ // SPDX-License-Identifier: AGPL-3.0 import * as React from 'react'; -import { StyleRulesCallback, WithStyles, Typography, withStyles, Theme } from '@material-ui/core'; +import { StyleRulesCallback, WithStyles, Typography, withStyles } from '@material-ui/core'; import { ArvadosTheme } from '~/common/custom-theme'; import * as classNames from 'classnames'; diff --git a/src/components/collection-panel-files/collection-panel-files.tsx b/src/components/collection-panel-files/collection-panel-files.tsx index 581c3a76..028dfb0c 100644 --- a/src/components/collection-panel-files/collection-panel-files.tsx +++ b/src/components/collection-panel-files/collection-panel-files.tsx @@ -3,11 +3,11 @@ // SPDX-License-Identifier: AGPL-3.0 import * as React from 'react'; -import { TreeItem, TreeItemStatus } from '../tree/tree'; -import { FileTreeData } from '../file-tree/file-tree-data'; -import { FileTree } from '../file-tree/file-tree'; +import { TreeItem, TreeItemStatus } from '~/components/tree/tree'; +import { FileTreeData } from '~/components/file-tree/file-tree-data'; +import { FileTree } from '~/components/file-tree/file-tree'; import { IconButton, Grid, Typography, StyleRulesCallback, withStyles, WithStyles, CardHeader, Card, Button, Tooltip } from '@material-ui/core'; -import { CustomizeTableIcon } from '../icon/icon'; +import { CustomizeTableIcon } from '~/components/icon/icon'; import { DownloadIcon } from '~/components/icon/icon'; export interface CollectionPanelFilesProps { diff --git a/src/components/tree/tree.tsx b/src/components/tree/tree.tsx index c64a7221..5b070b70 100644 --- a/src/components/tree/tree.tsx +++ b/src/components/tree/tree.tsx @@ -107,7 +107,7 @@ export const Tree = withStyles(styles)( const { levelIndentation = 20, itemRightPadding = 20 } = this.props; - return + return {items && items.map((it: TreeItem, idx: number) =>
; + graph?: Array; + $graph?: Array; } export interface Workflow { class: 'Workflow'; @@ -122,11 +123,19 @@ export const parseWorkflowDefinition = (workflow: WorkflowResource): WorkflowRes }; export const getWorkflowInputs = (workflowDefinition: WorkflowResoruceDefinition) => { - const mainWorkflow = workflowDefinition.$graph.find(item => item.class === 'Workflow' && item.id === '#main'); - return mainWorkflow - ? mainWorkflow.inputs - : undefined; + if (workflowDefinition.graph) { + const mainWorkflow = workflowDefinition.graph.find(item => item.class === 'Workflow' && item.id === '#main'); + return mainWorkflow + ? mainWorkflow.inputs + : undefined; + } else { + const mainWorkflow = workflowDefinition.$graph!.find(item => item.class === 'Workflow' && item.id === '#main'); + return mainWorkflow + ? mainWorkflow.inputs + : undefined; + } }; + export const getInputLabel = (input: CommandInputParameter) => { return `${input.label || input.id}`; }; diff --git a/src/store/context-menu/context-menu-actions.ts b/src/store/context-menu/context-menu-actions.ts index ca89f3eb..269495e5 100644 --- a/src/store/context-menu/context-menu-actions.ts +++ b/src/store/context-menu/context-menu-actions.ts @@ -36,6 +36,7 @@ export type ContextMenuResource = { menuKind: ContextMenuKind; isTrashed?: boolean; outputUuid?: string; + workflowUuid?: string; }; export const isKeyboardClick = (event: React.MouseEvent) => event.nativeEvent.detail === 0; @@ -187,6 +188,7 @@ export const openProcessContextMenu = (event: React.MouseEvent, pro name: res.name, description: res.description, outputUuid: res.outputUuid || '', + workflowUuid: res.properties.workflowUuid || '', menuKind: ContextMenuKind.PROCESS })); } diff --git a/src/store/process-panel/process-panel-actions.ts b/src/store/process-panel/process-panel-actions.ts index 42a718bd..f1f74f6d 100644 --- a/src/store/process-panel/process-panel-actions.ts +++ b/src/store/process-panel/process-panel-actions.ts @@ -26,6 +26,7 @@ export const loadProcessPanel = (uuid: string) => (dispatch: Dispatch) => { dispatch(loadProcess(uuid)); dispatch(initProcessPanelFilters); + dispatch(showWorkflowDetails(uuid)); }; export const navigateToOutput = (uuid: string) => @@ -41,7 +42,6 @@ export const navigateToOutput = (uuid: string) => export const openWorkflow = (uuid: string) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { dispatch(navigateToWorkflows); - dispatch(showWorkflowDetails(uuid)); }; export const initProcessPanelFilters = procesPanelActions.SET_PROCESS_PANEL_FILTERS([ diff --git a/src/store/processes/process-input-actions.ts b/src/store/processes/process-input-actions.ts index 98899a42..ab3f3bdb 100644 --- a/src/store/processes/process-input-actions.ts +++ b/src/store/processes/process-input-actions.ts @@ -15,7 +15,7 @@ export const openProcessInputDialog = (processUuid: string) => const process = getProcess(processUuid)(getState().resources); if (process) { const data: any = process; - if (data && data.containerRequest.mounts.varLibCwlWorkflowJson && data.containerRequest.mounts.varLibCwlWorkflowJson.content.graph[1].inputs.length > 0) { + if (data && data.containerRequest.mounts.varLibCwlWorkflowJson && data.containerRequest.mounts.varLibCwlWorkflowJson.content.graph[1] && data.containerRequest.mounts.varLibCwlWorkflowJson.content.graph[1].inputs.length > 0) { dispatch(dialogActions.OPEN_DIALOG({ id: PROCESS_INPUT_DIALOG_NAME, data })); } else { dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'There are no inputs in this process!', kind: SnackbarKind.ERROR })); diff --git a/src/store/processes/processes-actions.ts b/src/store/processes/processes-actions.ts index fa81da9f..2c7b8cff 100644 --- a/src/store/processes/processes-actions.ts +++ b/src/store/processes/processes-actions.ts @@ -10,11 +10,20 @@ import { FilterBuilder } from '~/services/api/filter-builder'; import { ContainerRequestResource } from '~/models/container-request'; import { Process } from './process'; import { dialogActions } from '~/store/dialog/dialog-actions'; -import {snackbarActions, SnackbarKind} from '~/store/snackbar/snackbar-actions'; +import { snackbarActions, SnackbarKind } from '~/store/snackbar/snackbar-actions'; import { projectPanelActions } from '~/store/project-panel/project-panel-action'; +import { navigateToRunProcess } from '~/store/navigation/navigation-action'; +import { goToStep, runProcessPanelActions } from '~/store/run-process-panel/run-process-panel-actions'; +import { getResource } from '~/store/resources/resources'; +import { getInputValue } from "~/views-components/process-input-dialog/process-input-dialog"; +import { initialize } from "redux-form"; +import { RUN_PROCESS_BASIC_FORM, RunProcessBasicFormData } from "~/views/run-process-panel/run-process-basic-form"; +import { RunProcessAdvancedFormData, RUN_PROCESS_ADVANCED_FORM } from "~/views/run-process-panel/run-process-advanced-form"; export const loadProcess = (containerRequestUuid: string) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise => { + const response = await services.workflowService.list(); + dispatch(runProcessPanelActions.SET_WORKFLOWS(response.items)); const containerRequest = await services.containerRequestService.get(containerRequestUuid); dispatch(updateResources([containerRequest])); if (containerRequest.containerUuid) { @@ -58,6 +67,44 @@ export const loadContainers = (filters: string) => return items; }; +export const reRunProcess = (processUuid: string, workflowUuid: string) => + (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + const process = getResource(processUuid)(getState().resources); + const workflows = getState().runProcessPanel.searchWorkflows; + const workflow = workflows.find(workflow => workflow.uuid === workflowUuid); + if (workflow && process) { + const newValues = getInputs(process); + process.mounts.varLibCwlWorkflowJson.content.graph[1].inputs = newValues; + const stringifiedDefinition = JSON.stringify(process.mounts.varLibCwlWorkflowJson.content); + const newWorkflow = { ...workflow, definition: stringifiedDefinition }; + + const basicInitialData: RunProcessBasicFormData = { name: `Copy of: ${process.name}`, description: process.description }; + dispatch(initialize(RUN_PROCESS_BASIC_FORM, basicInitialData)); + + const advancedInitialData: RunProcessAdvancedFormData = { + output: process.outputName, + runtime: process.schedulingParameters.maxRunTime, + ram: process.runtimeConstraints.ram, + vcpus: process.runtimeConstraints.vcpus, + keepCacheRam: process.runtimeConstraints.keepCacheRam, + api: process.runtimeConstraints.API + }; + dispatch(initialize(RUN_PROCESS_ADVANCED_FORM, advancedInitialData)); + + dispatch(navigateToRunProcess); + dispatch(goToStep(1)); + dispatch(runProcessPanelActions.SET_STEP_CHANGED(true)); + dispatch(runProcessPanelActions.SET_SELECTED_WORKFLOW(newWorkflow)); + } else { + dispatch(snackbarActions.OPEN_SNACKBAR({ message: `You can't re-run this process`, kind: SnackbarKind.ERROR })); + } + }; + +const getInputs = (data: any) => + data && data.mounts.varLibCwlWorkflowJson ? data.mounts.varLibCwlWorkflowJson.content.graph[1].inputs.map((it: any) => ( + { type: it.type, id: it.id, label: it.label, default: getInputValue(it.id, data.mounts.varLibCwlCwlInputJson.content), doc: it.doc } + )) : []; + export const openRemoveProcessDialog = (uuid: string) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { dispatch(dialogActions.OPEN_DIALOG({ @@ -74,11 +121,11 @@ export const openRemoveProcessDialog = (uuid: string) => export const REMOVE_PROCESS_DIALOG = 'removeProcessDialog'; export const removeProcessPermanently = (uuid: string) => - async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) =>{ + async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removing ...', kind: SnackbarKind.INFO })); await services.containerRequestService.delete(uuid); dispatch(projectPanelActions.REQUEST_ITEMS()); dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removed.', hideDuration: 2000, kind: SnackbarKind.SUCCESS })); }; - + diff --git a/src/store/run-process-panel/run-process-panel-actions.ts b/src/store/run-process-panel/run-process-panel-actions.ts index f7649860..12601729 100644 --- a/src/store/run-process-panel/run-process-panel-actions.ts +++ b/src/store/run-process-panel/run-process-panel-actions.ts @@ -149,6 +149,10 @@ export const runProcess = async (dispatch: Dispatch, getState: () => RootSt outputPath: '/var/spool/cwl', priority: 1, outputName: advancedForm[OUTPUT_FIELD] ? advancedForm[OUTPUT_FIELD] : undefined, + properties: { + workflowUuid: selectedWorkflow.uuid, + workflowName: selectedWorkflow.name + } }; const newProcess = await services.containerRequestService.create(newProcessData); dispatch(navigateToProcess(newProcess.uuid)); diff --git a/src/store/workflow-panel/workflow-panel-actions.ts b/src/store/workflow-panel/workflow-panel-actions.ts index 3d51cbb8..2f00bba6 100644 --- a/src/store/workflow-panel/workflow-panel-actions.ts +++ b/src/store/workflow-panel/workflow-panel-actions.ts @@ -7,11 +7,12 @@ 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'; -import { getResource } from '../resources/resources'; +import { getResource } from '~/store/resources/resources'; import { getProperty } from '~/store/properties/properties'; import { WorkflowResource } from '~/models/workflow'; import { navigateToRunProcess } from '~/store/navigation/navigation-action'; import { goToStep, runProcessPanelActions } from '~/store/run-process-panel/run-process-panel-actions'; +import { snackbarActions } from '~/store/snackbar/snackbar-actions'; export const WORKFLOW_PANEL_ID = "workflowPanel"; const UUID_PREFIX_PROPERTY_NAME = 'uuidPrefix'; @@ -33,14 +34,17 @@ export const getUuidPrefix = (state: RootState) => { }; export const openRunProcess = (uuid: string) => - async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { const workflows = getState().runProcessPanel.searchWorkflows; const workflow = workflows.find(workflow => workflow.uuid === uuid); - dispatch(navigateToRunProcess); - dispatch(runProcessPanelActions.RESET_RUN_PROCESS_PANEL()); - dispatch(goToStep(1)); - dispatch(runProcessPanelActions.SET_STEP_CHANGED(true)); - dispatch(runProcessPanelActions.SET_SELECTED_WORKFLOW(workflow!)); + if (workflow) { + dispatch(navigateToRunProcess); + dispatch(goToStep(1)); + dispatch(runProcessPanelActions.SET_STEP_CHANGED(true)); + dispatch(runProcessPanelActions.SET_SELECTED_WORKFLOW(workflow)); + } else { + dispatch(snackbarActions.OPEN_SNACKBAR({ message: `You can't run this process` })); + } }; export const getPublicUserUuid = (state: RootState) => { diff --git a/src/views-components/context-menu/action-sets/process-action-set.ts b/src/views-components/context-menu/action-sets/process-action-set.ts index 05242fbc..ed526968 100644 --- a/src/views-components/context-menu/action-sets/process-action-set.ts +++ b/src/views-components/context-menu/action-sets/process-action-set.ts @@ -19,8 +19,9 @@ import { openSharingDialog } from "~/store/sharing-dialog/sharing-dialog-actions import { openAdvancedTabDialog } from "~/store/advanced-tab/advanced-tab"; import { openProcessInputDialog } from "~/store/processes/process-input-actions"; import { toggleDetailsPanel } from '~/store/details-panel/details-panel-action'; -import { openRemoveProcessDialog } from "~/store/processes/processes-actions"; +import { openRemoveProcessDialog, reRunProcess } from "~/store/processes/processes-actions"; import { navigateToOutput } from "~/store/process-panel/process-panel-actions"; +import { snackbarActions, SnackbarKind } from "~/store/snackbar/snackbar-actions"; export const processActionSet: ContextMenuActionSet = [[ { @@ -63,8 +64,12 @@ export const processActionSet: ContextMenuActionSet = [[ icon: ReRunProcessIcon, name: "Re-run process", execute: (dispatch, resource) => { - // add code - } + if(resource.workflowUuid) { + dispatch(reRunProcess(resource.uuid, resource.workflowUuid)); + } else { + dispatch(snackbarActions.OPEN_SNACKBAR({ message: `You can't re-run this process`, hideDuration: 2000, kind: SnackbarKind.ERROR })); + } + } }, { icon: InputIcon, diff --git a/src/views-components/process-input-dialog/process-input-dialog.tsx b/src/views-components/process-input-dialog/process-input-dialog.tsx index 4ba7fd70..2ad0a57f 100644 --- a/src/views-components/process-input-dialog/process-input-dialog.tsx +++ b/src/views-components/process-input-dialog/process-input-dialog.tsx @@ -36,7 +36,7 @@ const getInputs = (data: any) => { type: it.type, id: it.id, label: it.label, value: getInputValue(it.id, data.mounts.varLibCwlCwlInputJson.content), disabled: true } )) : []; -const getInputValue = (id: string, data: any) => { +export const getInputValue = (id: string, data: any) => { switch (id) { case "#main/example_flag": return data.exampleFlag; diff --git a/src/views/process-panel/process-information-card.tsx b/src/views/process-panel/process-information-card.tsx index 1f42db6b..db0769f1 100644 --- a/src/views/process-panel/process-information-card.tsx +++ b/src/views/process-panel/process-information-card.tsx @@ -13,7 +13,7 @@ import { DetailsAttribute } from '~/components/details-attribute/details-attribu import { Process } from '~/store/processes/process'; import { getProcessStatus, getProcessStatusColor } from '~/store/processes/process'; import { formatDate } from '~/common/formatters'; -import { openWorkflow } from "~/store/process-panel/process-panel-actions"; +import * as classNames from 'classnames'; type CssRules = 'card' | 'iconHeader' | 'label' | 'value' | 'chip' | 'link' | 'content' | 'title' | 'avatar'; @@ -70,13 +70,13 @@ export interface ProcessInformationCardDataProps { onContextMenu: (event: React.MouseEvent) => void; openProcessInputDialog: (uuid: string) => void; navigateToOutput: (uuid: string) => void; - navigateToWorkflow: (uuid: string) => void; + openWorkflow: (uuid: string) => void; } type ProcessInformationCardProps = ProcessInformationCardDataProps & WithStyles; export const ProcessInformationCard = withStyles(styles, { withTheme: true })( - ({ classes, process, onContextMenu, theme, openProcessInputDialog, navigateToOutput, navigateToWorkflow }: ProcessInformationCardProps) => { + ({ classes, process, onContextMenu, theme, openProcessInputDialog, navigateToOutput, openWorkflow }: ProcessInformationCardProps) => { const { container } = process; const startedAt = container ? formatDate(container.startedAt) : 'N/A'; const finishedAt = container ? formatDate(container.finishedAt) : 'N/A'; @@ -86,17 +86,17 @@ export const ProcessInformationCard = withStyles(styles, { withTheme: true })( content: classes.title, avatar: classes.avatar }} - avatar={} + avatar={} action={
+ className={classes.chip} + style={{ backgroundColor: getProcessStatusColor(getProcessStatus(process), theme as ArvadosTheme) }} /> onContextMenu(event)}> - +
@@ -113,28 +113,28 @@ export const ProcessInformationCard = withStyles(styles, { withTheme: true })( {getDescription(process)} - }/> + } /> + label='From' + value={process.container ? formatDate(startedAt) : 'N/A'} /> - {process.containerRequest.properties.templateUuid && - navigateToWorkflow(process.containerRequest.properties.templateUuid)} - />} + label='To' + value={process.container ? formatDate(finishedAt) : 'N/A'} /> + {process.containerRequest.properties.workflowUuid && + openWorkflow(process.containerRequest.properties.workflowUuid)}> + + } navigateToOutput(process.containerRequest.outputUuid!)}> - + openProcessInputDialog(process.containerRequest.uuid)}> - + diff --git a/src/views/process-panel/process-panel-root.tsx b/src/views/process-panel/process-panel-root.tsx index 5fb6390c..eb8440f3 100644 --- a/src/views/process-panel/process-panel-root.tsx +++ b/src/views/process-panel/process-panel-root.tsx @@ -38,7 +38,7 @@ export const ProcessPanelRoot = ({ process, ...props }: ProcessPanelRootProps) = onContextMenu={event => props.onContextMenu(event, process)} openProcessInputDialog={props.openProcessInputDialog} navigateToOutput={props.navigateToOutput} - navigateToWorkflow={props.navigateToWorkflow} + openWorkflow={props.navigateToWorkflow} /> -- 2.30.2