merge conflicts
authorPawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>
Tue, 2 Oct 2018 13:18:52 +0000 (15:18 +0200)
committerPawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>
Tue, 2 Oct 2018 13:18:52 +0000 (15:18 +0200)
Feature #14231

Arvados-DCO-1.1-Signed-off-by: Pawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>

41 files changed:
.env
package.json
src/components/icon/icon.tsx
src/components/text-field/text-field.tsx
src/components/workflow-inputs-form/validators.ts [new file with mode: 0644]
src/components/workflow-inputs-form/workflow-input.tsx [new file with mode: 0644]
src/index.tsx
src/models/container-request.ts
src/models/mount-types.ts
src/models/process.ts
src/models/resource.ts
src/models/runtime-constraints.ts
src/models/workflow.ts
src/services/common-service/common-resource-service.ts
src/services/container-request-service/container-request-service.ts
src/services/services.ts
src/services/workflow-service/workflow-service.ts
src/store/run-process-panel/run-process-panel-actions.ts
src/store/run-process-panel/run-process-panel-reducer.ts
src/store/workflow-panel/workflow-panel-actions.ts
src/validators/is-float.tsx [new file with mode: 0644]
src/validators/is-integer.tsx [new file with mode: 0644]
src/validators/is-number.tsx [new file with mode: 0644]
src/views-components/side-panel-button/side-panel-button.tsx
src/views/run-process-panel/inputs/boolean-input.tsx [new file with mode: 0644]
src/views/run-process-panel/inputs/enum-input.tsx [new file with mode: 0644]
src/views/run-process-panel/inputs/file-input.tsx [new file with mode: 0644]
src/views/run-process-panel/inputs/float-input.tsx [new file with mode: 0644]
src/views/run-process-panel/inputs/generic-input.tsx [new file with mode: 0644]
src/views/run-process-panel/inputs/int-input.tsx [new file with mode: 0644]
src/views/run-process-panel/inputs/string-input.tsx [new file with mode: 0644]
src/views/run-process-panel/run-process-advanced-form.tsx [new file with mode: 0644]
src/views/run-process-panel/run-process-basic-form.tsx [new file with mode: 0644]
src/views/run-process-panel/run-process-inputs-form.tsx [new file with mode: 0644]
src/views/run-process-panel/run-process-panel-root.tsx
src/views/run-process-panel/run-process-panel.tsx
src/views/run-process-panel/run-process-second-step.tsx
src/views/workflow-panel/workflow-description-card.tsx
src/views/workflow-panel/workflow-panel-view.tsx
src/views/workflow-panel/workflow-panel.tsx
yarn.lock

diff --git a/.env b/.env
index ed397c5cc0952badd931be8ccfdd7ec60d95a6d1..bd410081ebecca5b03e42a2695e9e842ee013401 100644 (file)
--- a/.env
+++ b/.env
@@ -3,5 +3,5 @@
 # SPDX-License-Identifier: AGPL-3.0
 
 REACT_APP_ARVADOS_CONFIG_URL=/config.json
-REACT_APP_ARVADOS_API_HOST=qr1hi.arvadosapi.com
+REACT_APP_ARVADOS_API_HOST=c97qk.arvadosapi.com
 HTTPS=true
\ No newline at end of file
index c5ffc092d94f6b8655c26a28b978e19cb340c625..af5b28bd1526d069626e99ce63e2fb80821c70c7 100644 (file)
@@ -3,6 +3,7 @@
   "version": "0.1.0",
   "private": true,
   "dependencies": {
+    "@types/js-yaml": "3.11.2",
     "@material-ui/core": "3.1.1",
     "@material-ui/icons": "3.0.1",
     "@types/lodash": "4.14.116",
@@ -12,6 +13,7 @@
     "@types/shell-quote": "1.6.0",
     "axios": "0.18.0",
     "classnames": "2.2.6",
+    "js-yaml": "3.12.0",
     "lodash": "4.17.11",
     "react": "16.5.2",
     "react-copy-to-clipboard": "5.0.1",
index 06a56172ffb52077d48e6c05734b5a627dd4b697..5d99aea93b76b12b17b2367ad570f01a4439c6ce 100644 (file)
@@ -20,6 +20,7 @@ import CreateNewFolder from '@material-ui/icons/CreateNewFolder';
 import Delete from '@material-ui/icons/Delete';
 import DeviceHub from '@material-ui/icons/DeviceHub';
 import Edit from '@material-ui/icons/Edit';
+import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
 import Folder from '@material-ui/icons/Folder';
 import GetApp from '@material-ui/icons/GetApp';
 import Help from '@material-ui/icons/Help';
@@ -62,6 +63,7 @@ export const CloudUploadIcon: IconType = (props) => <CloudUpload {...props} />;
 export const DefaultIcon: IconType = (props) => <RateReview {...props} />;
 export const DetailsIcon: IconType = (props) => <Info {...props} />;
 export const DownloadIcon: IconType = (props) => <GetApp {...props} />;
+export const ExpandIcon: IconType = (props) => <ExpandMoreIcon {...props} />;
 export const FavoriteIcon: IconType = (props) => <Star {...props} />;
 export const HelpIcon: IconType = (props) => <Help {...props} />;
 export const HelpOutlineIcon: IconType = (props) => <HelpOutline {...props} />;
index 076889eabb4b9af6acb630d243e9db9829a47950..4d8c012f9edec158ada5549b9b3cfde0dde3b368 100644 (file)
@@ -16,7 +16,7 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     },
 });
 
-export const TextField = withStyles(styles)((props: WrappedFieldProps & WithStyles<CssRules> & { label?: string, autoFocus?: boolean }) =>
+export const TextField = withStyles(styles)((props: WrappedFieldProps & WithStyles<CssRules> & { label?: string, autoFocus?: boolean, required?: boolean }) =>
     <MaterialTextField
         helperText={props.meta.touched && props.meta.error}
         className={props.classes.textField}
@@ -26,6 +26,7 @@ export const TextField = withStyles(styles)((props: WrappedFieldProps & WithStyl
         autoComplete='off'
         autoFocus={props.autoFocus}
         fullWidth={true}
+        required={props.required}
         {...props.input}
     />);
 
diff --git a/src/components/workflow-inputs-form/validators.ts b/src/components/workflow-inputs-form/validators.ts
new file mode 100644 (file)
index 0000000..9e1e24e
--- /dev/null
@@ -0,0 +1,21 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { CommandInputParameter } from '~/models/workflow';
+import { require } from '~/validators/require';
+import { CWLType } from '../../models/workflow';
+
+
+const alwaysValid = () => undefined;
+
+export const required = ({ type }: CommandInputParameter) => {
+    if (type instanceof Array) {
+        for (const t of type) {
+            if (t === CWLType.NULL) {
+                return alwaysValid;
+            }
+        }
+    }
+    return require;
+};
diff --git a/src/components/workflow-inputs-form/workflow-input.tsx b/src/components/workflow-inputs-form/workflow-input.tsx
new file mode 100644 (file)
index 0000000..3a15d15
--- /dev/null
@@ -0,0 +1,18 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { CommandInputParameter } from '~/models/workflow';
+import { TextField } from '@material-ui/core';
+import { required } from '~/components/workflow-inputs-form/validators';
+
+export interface WorkflowInputProps {
+    input: CommandInputParameter;
+}
+export const WorkflowInput = ({ input }: WorkflowInputProps) =>
+    <TextField
+        label={`${input.label || input.id}${required(input)() ? '*' : ''}`}
+        name={input.id}
+        helperText={input.doc}
+        fullWidth />;
\ No newline at end of file
index d0154b663e0a9150419edbf437ac1b1eb729405d..a4a5e366a26b1c93713e580c4ce717a1c4e4a117 100644 (file)
@@ -40,6 +40,8 @@ import { processResourceActionSet } from '~/views-components/context-menu/action
 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';
+import { ContainerRequestState } from '~/models/container-request';
+import { MountKind } from './models/mount-types';
 
 const getBuildNumber = () => "BN-" + (process.env.REACT_APP_BUILD_NUMBER || "dev");
 const getGitCommit = () => "GIT-" + (process.env.REACT_APP_GIT_COMMIT || "latest").substr(0, 7);
@@ -111,8 +113,145 @@ const initListener = (history: History, store: RootStore, services: ServiceRepos
             initWebSocket(config, services.authService, store);
             await store.dispatch(loadWorkbench());
             addRouteChangeHandlers(history, store);
+            // createEnumCollectorWorkflow(services);
         }
     };
 };
 
+const createPrimitivesCollectorWorkflow = ({workflowService}:ServiceRepository) => {
+    workflowService.create({
+            name: 'Primitive values collector',
+            description: 'Workflow for collecting primitive values',
+            definition: "cwlVersion: v1.0\n$graph:\n- class: CommandLineTool\n  requirements:\n  - listing:\n    - entryname: input_collector.log\n      entry: |\n        \"flag\":\n          $(inputs.example_flag)\n        \"string\":\n          $(inputs.example_string)\n        \"int\":\n          $(inputs.example_int)\n        \"long\":\n          $(inputs.example_long)\n        \"float\":\n          $(inputs.example_float)\n        \"double\":\n          $(inputs.example_double)\n    class: InitialWorkDirRequirement\n  inputs:\n  - type: double\n    id: '#input_collector.cwl/example_double'\n  - type: boolean\n    id: '#input_collector.cwl/example_flag'\n  - type: float\n    id: '#input_collector.cwl/example_float'\n  - type: int\n    id: '#input_collector.cwl/example_int'\n  - type: long\n    id: '#input_collector.cwl/example_long'\n  - type: string\n    id: '#input_collector.cwl/example_string'\n  outputs:\n  - type: File\n    outputBinding:\n      glob: '*'\n    id: '#input_collector.cwl/output'\n  baseCommand: [echo]\n  id: '#input_collector.cwl'\n- class: Workflow\n  doc: Workflw for collecting primitive values\n  inputs:\n  - type: double\n    label: Double value\n    doc: This should allow for entering a decimal number (64-bit).\n    id: '#main/example_double'\n    default: 0.3333333333333333\n  - type: boolean\n    label: Boolean Flag\n    doc: This should render as in checkbox.\n    id: '#main/example_flag'\n    default: true\n  - type: float\n    label: Float value\n    doc: This should allow for entering a decimal number (32-bit).\n    id: '#main/example_float'\n    default: 0.15625\n  - type: int\n    label: Integer Number\n    doc: This should allow for entering a number (32-bit signed).\n    id: '#main/example_int'\n    default: 2147483647\n  - type: long\n    label: Long Number\n    doc: This should allow for entering a number (64-bit signed).\n    id: '#main/example_long'\n    default: 9223372036854775807\n  - type: string\n    label: Freetext\n    doc: This should allow for entering an arbitrary char sequence.\n    id: '#main/example_string'\n    default: This is a string\n  outputs:\n  - type: File\n    outputSource: '#main/input_collector/output'\n    id: '#main/log_file'\n  steps:\n  - run: '#input_collector.cwl'\n    in:\n    - source: '#main/example_double'\n      id: '#main/input_collector/example_double'\n    - source: '#main/example_flag'\n      id: '#main/input_collector/example_flag'\n    - source: '#main/example_float'\n      id: '#main/input_collector/example_float'\n    - source: '#main/example_int'\n      id: '#main/input_collector/example_int'\n    - source: '#main/example_long'\n      id: '#main/input_collector/example_long'\n    - source: '#main/example_string'\n      id: '#main/input_collector/example_string'\n    out: ['#main/input_collector/output']\n    id: '#main/input_collector'\n  id: '#main'\n",
+        });
+};
+
+const createEnumCollectorWorkflow = ({workflowService}:ServiceRepository) => {
+    workflowService.create({
+            name: 'Enum values collector',
+            description: 'Workflow for collecting enum values',
+            definition: "cwlVersion: v1.0\n$graph:\n- class: CommandLineTool\n  requirements:\n  - listing:\n    - entryname: input_collector.log\n      entry: |\n        \"enum_type\":\n          $(inputs.enum_type)\n\n    class: InitialWorkDirRequirement\n  inputs:\n  - type:\n      type: enum\n      symbols: ['#input_collector.cwl/enum_type/OTU table', '#input_collector.cwl/enum_type/Pathway\n          table', '#input_collector.cwl/enum_type/Function table', '#input_collector.cwl/enum_type/Ortholog\n          table']\n    id: '#input_collector.cwl/enum_type'\n  outputs:\n  - type: File\n    outputBinding:\n      glob: '*'\n    id: '#input_collector.cwl/output'\n  baseCommand: [echo]\n  id: '#input_collector.cwl'\n- class: Workflow\n  doc: This is the description of the workflow\n  inputs:\n  - type:\n      type: enum\n      symbols: ['#main/enum_type/OTU table', '#main/enum_type/Pathway table', '#main/enum_type/Function\n          table', '#main/enum_type/Ortholog table']\n      name: '#enum_typef4179c7f-45f9-482d-a5db-1abb86698384'\n    label: Enumeration Type\n    doc: This should render as a drop-down menu.\n    id: '#main/enum_type'\n    default: OTU table\n  outputs:\n  - type: File\n    outputSource: '#main/input_collector/output'\n    id: '#main/log_file'\n  steps:\n  - run: '#input_collector.cwl'\n    in:\n    - source: '#main/enum_type'\n      id: '#main/input_collector/enum_type'\n    out: ['#main/input_collector/output']\n    id: '#main/input_collector'\n  id: '#main'\n",
+        });
+};
+
+const createSampleProcess = ({ containerRequestService }: ServiceRepository) => {
+    containerRequestService.create({
+        ownerUuid: 'c97qk-j7d0g-s3ngc1z0748hsmf',
+        name: 'Simple process 7',
+        state: ContainerRequestState.COMMITTED,
+        mounts: {
+            '/var/spool/cwl': {
+                kind: MountKind.COLLECTION,
+                writable: true,
+            },
+            'stdout': {
+                kind: MountKind.MOUNTED_FILE,
+                path: '/var/spool/cwl/cwl.output.json'
+            },
+            '/var/lib/cwl/workflow.json': {
+                kind: MountKind.JSON,
+                content: {
+                    "cwlVersion": "v1.0",
+                    "$graph": [
+                        {
+                            "class": "CommandLineTool",
+                            "requirements": [
+                                {
+                                  "listing": [
+                                    {
+                                      "entryname": "input_collector.log",
+                                      "entry": "$(inputs.single_file.basename)\n"
+                                    }
+                                  ],
+                                  "class": "InitialWorkDirRequirement"
+                                }
+                              ],
+                            "inputs": [
+                                {
+                                    "type": "File",
+                                    "id": "#input_collector.cwl/single_file"
+                                }
+                            ],
+                            "outputs": [
+                                {
+                                    "type": "File",
+                                    "outputBinding": {
+                                        "glob": "*"
+                                    },
+                                    "id": "#input_collector.cwl/output"
+                                }
+                            ],
+                            "baseCommand": [
+                                "echo"
+                            ],
+                            "id": "#input_collector.cwl"
+                        },
+                        {
+                            "class": "Workflow",
+                            "doc": "This is the description of the workflow",
+                            "inputs": [
+                                {
+                                    "type": "File",
+                                    "label": "Single File",
+                                    "doc": "This should allow for single File selection only.",
+                                    "id": "#main/single_file"
+                                }
+                            ],
+                            "outputs": [
+                                {
+                                    "type": "File",
+                                    "outputSource": "#main/input_collector/output",
+                                    "id": "#main/log_file"
+                                }
+                            ],
+                            "steps": [
+                                {
+                                    "run": "#input_collector.cwl",
+                                    "in": [
+                                        {
+                                            "source": "#main/single_file",
+                                            "id": "#main/input_collector/single_file"
+                                        }
+                                    ],
+                                    "out": [
+                                        "#main/input_collector/output"
+                                    ],
+                                    "id": "#main/input_collector"
+                                }
+                            ],
+                            "id": "#main"
+                        }
+                    ]
+                },
+            },
+            '/var/lib/cwl/cwl.input.json': {
+                kind: MountKind.JSON,
+                content: {
+                    "single_file": {
+                        "class": "File",
+                        "location": "keep:233454526794c0a2d56a305baeff3d30+145/1.txt",
+                        "basename": "fileA"
+                      }
+                },
+            }
+        },
+        runtimeConstraints: {
+            API: true,
+            vcpus: 1,
+            ram: 1073741824,
+        },
+        containerImage: 'arvados/jobs:1.1.4.20180618144723',
+        cwd: '/var/spool/cwl',
+        command: [
+            'arvados-cwl-runner',
+            '--local',
+            '--api=containers',
+            "--project-uuid=c97qk-j7d0g-s3ngc1z0748hsmf",
+            '/var/lib/cwl/workflow.json#main',
+            '/var/lib/cwl/cwl.input.json'
+        ],
+        outputPath: '/var/spool/cwl',
+        priority: 1,
+    });
+};
 
index 78891c7bdcd8fc23918ff369052548dd02b725db..e65bed9fd3b729af13a0ee546efebfc250749215 100644 (file)
@@ -22,7 +22,7 @@ export interface ContainerRequestResource extends Resource {
     requestingContainerUuid: string | null;
     containerUuid: string | null;
     containerCountMax: number;
-    mounts: MountType[];
+    mounts: {[path: string]: MountType};
     runtimeConstraints: RuntimeConstraints;
     schedulingParameters: SchedulingParameters;
     containerImage: string;
index ec48c852450df5a239a546c3bc4c11c64d72806f..52b29499bbc6f91e3752ffc137b4e3aadd4ea4c3 100644 (file)
@@ -8,7 +8,7 @@ export enum MountKind {
     TEMPORARY_DIRECTORY = 'tmp',
     KEEP = 'keep',
     MOUNTED_FILE = 'file',
-    JSON = 'JSON'
+    JSON = 'json'
 }
 
 export type MountType =
@@ -16,7 +16,8 @@ export type MountType =
     GitTreeMount |
     TemporaryDirectoryMount |
     KeepMount |
-    JSONMount;
+    JSONMount |
+    FileMount;
 
 export interface CollectionMount {
     kind: MountKind.COLLECTION;
@@ -52,5 +53,10 @@ export interface KeepMount {
 
 export interface JSONMount {
     kind: MountKind.JSON;
-    content: string;
+    content: any;
+}
+
+export interface FileMount {
+    kind: MountKind.MOUNTED_FILE;
+    path: string;
 }
index 1e04cb10f395de5284765fbb50244d2e45d4ea1d..9762d50411d45c40be0bb9efbc389e10e3e2e35e 100644 (file)
@@ -3,5 +3,29 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { ContainerRequestResource } from "./container-request";
+import { MountType, MountKind } from '~/models/mount-types';
+import { WorkflowResource, parseWorkflowDefinition } from '~/models/workflow';
+import { WorkflowInputsData } from './workflow';
 
 export type ProcessResource = ContainerRequestResource;
+
+export const createWorkflowMounts = (workflow: WorkflowResource, inputs: WorkflowInputsData): { [path: string]: MountType } => {
+    return {
+        '/var/spool/cwl': {
+            kind: MountKind.COLLECTION,
+            writable: true,
+        },
+        'stdout': {
+            kind: MountKind.MOUNTED_FILE,
+            path: '/var/spool/cwl/cwl.output.json',
+        },
+        '/var/lib/cwl/workflow.json': {
+            kind: MountKind.JSON,
+            content: parseWorkflowDefinition(workflow)
+        },
+        '/var/lib/cwl/cwl.input.json': {
+            kind: MountKind.JSON,
+            content: inputs,
+        }
+    };
+};
index 7e40c738f8076b1aa10652411c4e97a2d13c09a2..b8156cf2d12c0c7990e5f8aa8e869626f8bf17bc 100644 (file)
@@ -40,7 +40,7 @@ export enum ResourceObjectType {
     GROUP = 'j7d0g',
     LOG = '57u5n',
     USER = 'tpzed',
-    WORKFLOW = '7fd4e'
+    WORKFLOW = '7fd4e',
 }
 
 export const RESOURCE_UUID_PATTERN = '.{5}-.{5}-.{15}';
index ba905378108f1a39a6456f0c9f774317a2ba8acb..a780fd3575c71e93efc1c267681a3b896a8d42a4 100644 (file)
@@ -5,6 +5,6 @@
 export interface RuntimeConstraints {
     ram: number;
     vcpus: number;
-    keepCacheRam: number;
+    keepCacheRam?: number;
     API: boolean;
 }
index d644b7909047b0c28db876f3dc1dbe9a96cd0c43..d7d97c4c962401bfc7bd5ff655ebe88fdaf4d92d 100644 (file)
 // SPDX-License-Identifier: AGPL-3.0
 
 import { Resource, ResourceKind } from "./resource";
+import { safeLoad } from 'js-yaml';
 
 export interface WorkflowResource extends Resource {
     kind: ResourceKind.WORKFLOW;
     name: string;
     description: string;
     definition: string;
-}
\ No newline at end of file
+}
+export interface WorkflowResoruceDefinition {
+    cwlVersion: string;
+    $graph: Array<Workflow | CommandLineTool>;
+}
+export interface Workflow {
+    class: 'Workflow';
+    doc?: string;
+    id?: string;
+    inputs: CommandInputParameter[];
+    outputs: any[];
+    steps: any[];
+}
+
+export interface CommandLineTool {
+    class: 'CommandLineTool';
+    id: string;
+    inputs: CommandInputParameter[];
+    outputs: any[];
+}
+
+export type CommandInputParameter =
+    BooleanCommandInputParameter |
+    IntCommandInputParameter |
+    LongCommandInputParameter |
+    FloatCommandInputParameter |
+    DoubleCommandInputParameter |
+    StringCommandInputParameter |
+    FileCommandInputParameter |
+    DirectoryCommandInputParameter |
+    StringArrayCommandInputParameter |
+    FileArrayCommandInputParameter |
+    DirectoryArrayCommandInputParameter |
+    EnumCommandInputParameter;
+
+export enum CWLType {
+    NULL = 'null',
+    BOOLEAN = 'boolean',
+    INT = 'int',
+    LONG = 'long',
+    FLOAT = 'float',
+    DOUBLE = 'double',
+    STRING = 'string',
+    FILE = 'File',
+    DIRECTORY = 'Directory',
+}
+
+export interface CommandInputEnumSchema {
+    symbols: string[];
+    type: 'enum';
+    label?: string;
+    name?: string;
+}
+
+export interface CommandInputArraySchema<ItemType> {
+    items: ItemType;
+    type: 'array';
+    label?: string;
+}
+
+export interface File {
+    class: CWLType.FILE;
+    location?: string;
+    path?: string;
+    basename?: string;
+}
+
+export interface Directory {
+    class: CWLType.DIRECTORY;
+    location?: string;
+    path?: string;
+    basename?: string;
+}
+
+export interface GenericCommandInputParameter<Type, Value> {
+    id: string;
+    label?: string;
+    doc?: string | string[];
+    default?: Value;
+    type?: Type | Array<Type | CWLType.NULL>;
+}
+export type GenericArrayCommandInputParameter<Type, Value> = GenericCommandInputParameter<CommandInputArraySchema<Type>, Value[]>;
+
+export type BooleanCommandInputParameter = GenericCommandInputParameter<CWLType.BOOLEAN, boolean>;
+export type IntCommandInputParameter = GenericCommandInputParameter<CWLType.INT, number>;
+export type LongCommandInputParameter = GenericCommandInputParameter<CWLType.LONG, number>;
+export type FloatCommandInputParameter = GenericCommandInputParameter<CWLType.FLOAT, number>;
+export type DoubleCommandInputParameter = GenericCommandInputParameter<CWLType.DOUBLE, number>;
+export type StringCommandInputParameter = GenericCommandInputParameter<CWLType.STRING, string>;
+export type FileCommandInputParameter = GenericCommandInputParameter<CWLType.FILE, File>;
+export type DirectoryCommandInputParameter = GenericCommandInputParameter<CWLType.DIRECTORY, Directory>;
+export type EnumCommandInputParameter = GenericCommandInputParameter<CommandInputEnumSchema, string>;
+
+export type StringArrayCommandInputParameter = GenericArrayCommandInputParameter<CWLType.STRING, string>;
+export type FileArrayCommandInputParameter = GenericArrayCommandInputParameter<CWLType.FILE, File>;
+export type DirectoryArrayCommandInputParameter = GenericArrayCommandInputParameter<CWLType.DIRECTORY, Directory>;
+
+export type WorkflowInputsData = {
+    [key: string]: boolean | number | string | File | Directory;
+};
+export const parseWorkflowDefinition = (workflow: WorkflowResource): WorkflowResoruceDefinition => {
+    const definition = safeLoad(workflow.definition);
+    return definition;
+};
+
+export const getWorkflowInputs = (workflowDefinition: WorkflowResoruceDefinition) => {
+    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}`;
+};
+
+export const isRequiredInput = ({ type }: CommandInputParameter) => {
+    if (type instanceof Array) {
+        for (const t of type) {
+            if (t === CWLType.NULL) {
+                return false;
+            }
+        }
+    }
+    return true;
+};
+
+export const isPrimitiveOfType = (input: GenericCommandInputParameter<any, any>, type: CWLType) =>
+    input.type instanceof Array
+        ? input.type.indexOf(type) > -1
+        : input.type === type;
+
+export const stringifyInputType = ({ type }: CommandInputParameter) => {
+    if (typeof type === 'string') {
+        return type;
+    } else if (type instanceof Array) {
+        return type.join(' | ');
+    } else if (typeof type === 'object') {
+        if (type.type === 'enum') {
+            return 'enum';
+        } else if (type.type === 'array') {
+            return `${type.items}[]`;
+        } else {
+            return 'unknown';
+        }
+    } else {
+        return 'unknown';
+    }
+};
index f6810c0453b183a1db0847fe127a8dd607004d41..0c99e7d4be10ce56c4b453e01448ad56254069fe 100644 (file)
@@ -89,7 +89,7 @@ export class CommonResourceService<T extends Resource> {
         this.actions = actions;
     }
 
-    create(data?: Partial<T> | any) {
+    create(data?: Partial<T>) {
         return CommonResourceService.defaultResponse(
             this.serverApi
                 .post<T>(this.resourceType, data && CommonResourceService.mapKeys(_.snakeCase)(data)),
index e035ed5328fbecfef416212daa1183cb5d51b748..2e2ccd1c851bdc031d0f3f0aba8b9badb5873b33 100644 (file)
@@ -2,6 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
+import { snakeCase } from 'lodash';
 import { CommonResourceService } from "~/services/common-service/common-resource-service";
 import { AxiosInstance } from "axios";
 import { ContainerRequestResource } from '~/models/container-request';
@@ -11,4 +12,25 @@ export class ContainerRequestService extends CommonResourceService<ContainerRequ
     constructor(serverApi: AxiosInstance, actions: ApiActions) {
         super(serverApi, "container_requests", actions);
     }
+
+    create(data?: Partial<ContainerRequestResource>) {
+        if (data) {
+            const { mounts } = data;
+            if (mounts) {
+                const mappedData = {
+                    ...CommonResourceService.mapKeys(snakeCase)(data),
+                    mounts,
+                };
+                return CommonResourceService
+                    .defaultResponse(
+                        this.serverApi.post<ContainerRequestResource>(this.resourceType, mappedData),
+                        this.actions);
+            }
+        }
+        return CommonResourceService
+            .defaultResponse(
+                this.serverApi
+                    .post<ContainerRequestResource>(this.resourceType, data && CommonResourceService.mapKeys(snakeCase)(data)),
+                this.actions);
+    }
 }
index 738b69de28584a5afdf0416eb428e20cf21e8c61..d39a68b91c20df7d4ce896ae62f826d74bd53249 100644 (file)
@@ -21,7 +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';
+import { WorkflowService } from "~/services/workflow-service/workflow-service";
 
 export type ServiceRepository = ReturnType<typeof createServices>;
 
@@ -66,7 +66,7 @@ export const createServices = (config: Config, actions: ApiActions) => {
         tagService,
         userService,
         webdavClient,
-        workflowService
+        workflowService,
     };
 };
 
index 60f898f112751bfbbf59b1840a1e3c50e6ba593d..57ad5fa40fc61394f084dcd8ca778ea7d82aa4b0 100644 (file)
@@ -3,9 +3,9 @@
 // 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";
+import { WorkflowResource } from '~/models/workflow';
+import { ApiActions } from '~/services/api/api-actions';
 
 export class WorkflowService extends CommonResourceService<WorkflowResource> {
     constructor(serverApi: AxiosInstance, actions: ApiActions) {
index 3a3f6b0500e29441186ca1543c7854b2f67363d7..7112f715b4c449d2ee7aa15ac6786f783a4c0d2b 100644 (file)
@@ -7,8 +7,17 @@ import { unionize, ofType, UnionOf } from "~/common/unionize";
 import { ServiceRepository } from "~/services/services";
 import { RootState } from '~/store/store';
 import { WorkflowResource } from '~/models/workflow';
+import { getFormValues } from 'redux-form';
+import { RUN_PROCESS_BASIC_FORM, RunProcessBasicFormData } from '~/views/run-process-panel/run-process-basic-form';
+import { RUN_PROCESS_INPUTS_FORM } from '~/views/run-process-panel/run-process-inputs-form';
+import { WorkflowInputsData } from '~/models/workflow';
+import { createWorkflowMounts } from '~/models/process';
+import { ContainerRequestState } from '~/models/container-request';
+import { navigateToProcess } from '../navigation/navigation-action';
+import { RunProcessAdvancedFormData, RUN_PROCESS_ADVANCED_FORM } from '~/views/run-process-panel/run-process-advanced-form';
 
 export const runProcessPanelActions = unionize({
+    SET_PROCESS_OWNER_UUID: ofType<string>(),
     SET_CURRENT_STEP: ofType<number>(),
     SET_WORKFLOWS: ofType<WorkflowResource[]>(),
     SET_SELECTED_WORKFLOW: ofType<WorkflowResource>(),
@@ -34,11 +43,53 @@ export const loadRunProcessPanel = () =>
         }
     };
 
-export const setWorkflow = (workflow: WorkflowResource) => 
+export const setWorkflow = (workflow: WorkflowResource) =>
     async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
         dispatch(runProcessPanelActions.SET_SELECTED_WORKFLOW(workflow));
     };
 
 export const goToStep = (step: number) => runProcessPanelActions.SET_CURRENT_STEP(step);
 
-export const searchWorkflows = (term: string) => runProcessPanelActions.SEARCH_WORKFLOWS(term);
\ No newline at end of file
+export const runProcess = async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
+    const state = getState();
+    const basicForm = getFormValues(RUN_PROCESS_BASIC_FORM)(state) as RunProcessBasicFormData;
+    const inputsForm = getFormValues(RUN_PROCESS_INPUTS_FORM)(state) as WorkflowInputsData;
+    const advancedForm = getFormValues(RUN_PROCESS_ADVANCED_FORM)(state) as RunProcessAdvancedFormData;
+    const { processOwnerUuid, selectedWorkflow } = state.runProcessPanel;
+    if (selectedWorkflow) {
+        const newProcessData = {
+            ownerUuid: processOwnerUuid,
+            name: basicForm.name,
+            description: basicForm.description,
+            state: ContainerRequestState.COMMITTED,
+            mounts: createWorkflowMounts(selectedWorkflow, normalizeInputKeys(inputsForm)),
+            runtimeConstraints: {
+                API: true,
+                vcpus: 1,
+                ram: 1073741824,
+            },
+            containerImage: 'arvados/jobs:1.1.4.20180618144723',
+            cwd: '/var/spool/cwl',
+            command: [
+                'arvados-cwl-runner',
+                '--local',
+                '--api=containers',
+                `--project-uuid=${processOwnerUuid}`,
+                '/var/lib/cwl/workflow.json#main',
+                '/var/lib/cwl/cwl.input.json'
+            ],
+            outputPath: '/var/spool/cwl',
+            priority: 1,
+            outputName: advancedForm && advancedForm.output ? advancedForm.output : undefined,
+        };
+        const newProcess = await services.containerRequestService.create(newProcessData);
+        dispatch(navigateToProcess(newProcess.uuid));
+    }
+};
+
+const normalizeInputKeys = (inputs: WorkflowInputsData): WorkflowInputsData =>
+    Object.keys(inputs).reduce((normalizedInputs, key) => ({
+        ...normalizedInputs,
+        [key.split('/').slice(1).join('/')]: inputs[key],
+    }), {});
+export const searchWorkflows = (term: string) => runProcessPanelActions.SEARCH_WORKFLOWS(term);
index 10cdaeb77e3de7280774e157dfef381d7305d319..0ad06bee58da253d83e2ad31fbad04a65b1bd7d6 100644 (file)
@@ -3,27 +3,36 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { RunProcessPanelAction, runProcessPanelActions } from '~/store/run-process-panel/run-process-panel-actions';
-import { WorkflowResource } from '~/models/workflow';
+import { WorkflowResource, CommandInputParameter, getWorkflowInputs, parseWorkflowDefinition } from '~/models/workflow';
 
 interface RunProcessPanel {
+    processOwnerUuid: string;
     currentStep: number;
     workflows: WorkflowResource[];
     searchWorkflows: WorkflowResource[];
     selectedWorkflow: WorkflowResource | undefined;
+    inputs: CommandInputParameter[];
 }
 
 const initialState: RunProcessPanel = {
+    processOwnerUuid: '',
     currentStep: 0,
     workflows: [],
+    selectedWorkflow: undefined,
+    inputs: [],
     searchWorkflows: [],
-    selectedWorkflow: undefined
 };
 
 export const runProcessPanelReducer = (state = initialState, action: RunProcessPanelAction): RunProcessPanel =>
     runProcessPanelActions.match(action, {
+        SET_PROCESS_OWNER_UUID: processOwnerUuid => ({ ...state, processOwnerUuid }),
         SET_CURRENT_STEP: currentStep => ({ ...state, currentStep }),
+        SET_SELECTED_WORKFLOW: selectedWorkflow => ({
+            ...state,
+            selectedWorkflow,
+            inputs: getWorkflowInputs(parseWorkflowDefinition(selectedWorkflow)) || [],
+        }),
         SET_WORKFLOWS: workflows => ({ ...state, workflows, searchWorkflows: workflows }), 
-        SET_SELECTED_WORKFLOW: selectedWorkflow => ({ ...state, selectedWorkflow }),
         SEARCH_WORKFLOWS: term => ({ ...state, searchWorkflows: state.workflows.filter(workflow => workflow.name.includes(term)) }),
         default: () => state
     });
\ No newline at end of file
index aa79347cc67b0aca3ecbc80384f6533eac44188d..ca72e5a51a474fc8134d7f7aa6d144bb5a28bd9c 100644 (file)
@@ -7,10 +7,13 @@ 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 { getProperty } from '~/store/properties/properties';
+import { WorkflowResource } from '../../models/workflow';
 
 export const WORKFLOW_PANEL_ID = "workflowPanel";
 const UUID_PREFIX_PROPERTY_NAME = 'uuidPrefix';
-
+const WORKFLOW_PANEL_DETAILS_UUID = 'workflowPanelDetailsUuid';
 export const workflowPanelActions = bindDataExplorerActions(WORKFLOW_PANEL_ID);
 
 export const loadWorkflowPanel = () =>
@@ -21,6 +24,14 @@ export const loadWorkflowPanel = () =>
 export const setUuidPrefix = (uuidPrefix: string) =>
     propertiesActions.SET_PROPERTY({ key: UUID_PREFIX_PROPERTY_NAME, value: uuidPrefix });
 
-export const getUuidPrefix = (state: RootState) =>{
+export const getUuidPrefix = (state: RootState) => {
     return state.properties.uuidPrefix;
-};
\ No newline at end of file
+};
+
+export const showWorkflowDetails = (uuid: string) =>
+    propertiesActions.SET_PROPERTY({ key: WORKFLOW_PANEL_DETAILS_UUID, value: uuid });
+
+export const getWorkflowDetails = (state: RootState) => {
+    const uuid = getProperty<string>(WORKFLOW_PANEL_DETAILS_UUID)(state.properties);
+    return uuid ? getResource<WorkflowResource>(uuid)(state.resources) : undefined;
+};
diff --git a/src/validators/is-float.tsx b/src/validators/is-float.tsx
new file mode 100644 (file)
index 0000000..9bde5f9
--- /dev/null
@@ -0,0 +1,11 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { isInteger, isNumber } from 'lodash';
+
+const ERROR_MESSAGE = 'This field must be a float';
+
+export const isFloat = (value: any) => {
+    return isNumber(value) ? undefined : ERROR_MESSAGE;
+};
diff --git a/src/validators/is-integer.tsx b/src/validators/is-integer.tsx
new file mode 100644 (file)
index 0000000..fbfe8fb
--- /dev/null
@@ -0,0 +1,11 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { isInteger as isInt } from 'lodash';
+
+const ERROR_MESSAGE = 'This field must be an integer';
+
+export const isInteger = (value: any) => {
+    return isInt(value) ? undefined : ERROR_MESSAGE;
+};
diff --git a/src/validators/is-number.tsx b/src/validators/is-number.tsx
new file mode 100644 (file)
index 0000000..152c647
--- /dev/null
@@ -0,0 +1,10 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { isNumber as isNum } from 'lodash';
+const ERROR_MESSAGE = 'This field must be a number';
+
+export const isNumber = (value: any) => {
+    return !isNaN(value) && isNum(value) ? undefined : ERROR_MESSAGE;
+};
index 2e8433a9ddb074dedb405ba205abbadddb078803..9ba23267dd3a1025a6f2f96fec956c86e2090a9f 100644 (file)
@@ -15,6 +15,7 @@ import { openProjectCreateDialog } from '~/store/projects/project-create-actions
 import { openCollectionCreateDialog } from '~/store/collections/collection-create-actions';
 import { matchProjectRoute } from '~/routes/routes';
 import { navigateToRunProcess } from '~/store/navigation/navigation-action';
+import { runProcessPanelActions } from '~/store/run-process-panel/run-process-panel-actions';
 
 type CssRules = 'button' | 'menuItem' | 'icon';
 
@@ -67,10 +68,10 @@ export const SidePanelButton = withStyles(styles)(
             };
 
             render() {
-                const { classes, buttonVisible  } = this.props;
+                const { classes, buttonVisible } = this.props;
                 const { anchorEl } = this.state;
                 return <Toolbar>
-                    {buttonVisible  && <Grid container>
+                    {buttonVisible && <Grid container>
                         <Grid container item xs alignItems="center" justify="flex-start">
                             <Button variant="contained" color="primary" size="small" className={classes.button}
                                 aria-owns={anchorEl ? 'aside-menu-list' : undefined}
@@ -97,7 +98,7 @@ export const SidePanelButton = withStyles(styles)(
                                 </MenuItem>
                             </Menu>
                         </Grid>
-                    </Grid> }
+                    </Grid>}
                 </Toolbar>;
             }
 
@@ -106,6 +107,7 @@ export const SidePanelButton = withStyles(styles)(
             }
 
             handleRunProcessClick = () => {
+                this.props.dispatch(runProcessPanelActions.SET_PROCESS_OWNER_UUID(this.props.currentItemId));
                 this.props.dispatch<any>(navigateToRunProcess);
             }
 
diff --git a/src/views/run-process-panel/inputs/boolean-input.tsx b/src/views/run-process-panel/inputs/boolean-input.tsx
new file mode 100644 (file)
index 0000000..e66ec3d
--- /dev/null
@@ -0,0 +1,31 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { BooleanCommandInputParameter } from '~/models/workflow';
+import { Field } from 'redux-form';
+import { Switch } from '@material-ui/core';
+import { GenericInputProps, GenericInput } from './generic-input';
+
+export interface BooleanInputProps {
+    input: BooleanCommandInputParameter;
+}
+export const BooleanInput = ({ input }: BooleanInputProps) =>
+    <Field
+        name={input.id}
+        commandInput={input}
+        component={BooleanInputComponent}
+        normalize={(value, prevValue) => !prevValue}
+    />;
+
+const BooleanInputComponent = (props: GenericInputProps) =>
+    <GenericInput
+        component={Input}
+        {...props} />;
+
+const Input = (props: GenericInputProps) =>
+    <Switch
+        color='primary'
+        checked={props.input.value}
+        onChange={() => props.input.onChange(props.input.value)} />;
\ No newline at end of file
diff --git a/src/views/run-process-panel/inputs/enum-input.tsx b/src/views/run-process-panel/inputs/enum-input.tsx
new file mode 100644 (file)
index 0000000..967d8fc
--- /dev/null
@@ -0,0 +1,36 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { EnumCommandInputParameter, CommandInputEnumSchema } from '~/models/workflow';
+import { Field } from 'redux-form';
+import { Select, MenuItem } from '@material-ui/core';
+import { GenericInputProps, GenericInput } from './generic-input';
+
+export interface EnumInputProps {
+    input: EnumCommandInputParameter;
+}
+export const EnumInput = ({ input }: EnumInputProps) =>
+    <Field
+        name={input.id}
+        commandInput={input}
+        component={EnumInputComponent}
+    />;
+
+const EnumInputComponent = (props: GenericInputProps) =>
+    <GenericInput
+        component={Input}
+        {...props} />;
+
+const Input = (props: GenericInputProps) => {
+    const type = props.commandInput.type as CommandInputEnumSchema;
+    return <Select
+        value={props.input.value}
+        onChange={props.input.onChange}>
+        {type.symbols.map(symbol =>
+            <MenuItem key={symbol} value={symbol.split('/').pop()}>
+                {symbol.split('/').pop()}
+            </MenuItem>)}
+    </Select>;
+};
\ No newline at end of file
diff --git a/src/views/run-process-panel/inputs/file-input.tsx b/src/views/run-process-panel/inputs/file-input.tsx
new file mode 100644 (file)
index 0000000..7c4402f
--- /dev/null
@@ -0,0 +1,42 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import {
+    getInputLabel,
+    isRequiredInput,
+    FileCommandInputParameter,
+    File,
+    CWLType
+} from '~/models/workflow';
+import { Field } from 'redux-form';
+import { require } from '~/validators/require';
+import { Input } from '@material-ui/core';
+import { GenericInputProps, GenericInput } from './generic-input';
+
+export interface FileInputProps {
+    input: FileCommandInputParameter;
+}
+export const FileInput = ({ input }: FileInputProps) =>
+    <Field
+        name={input.id}
+        commandInput={input}        
+        component={FileInputComponent}
+        format={(value?: File) => value ? value.location : ''}
+        parse={(value: string): File => ({
+            class: CWLType.FILE,
+            location: value,
+            basename: value.split('/').slice(1).join('/')
+        })}
+        validate={[
+            isRequiredInput(input)
+                ? require
+                : () => undefined,
+        ]} />;
+
+const FileInputComponent = (props: GenericInputProps) =>
+    <GenericInput
+        component={props =>
+            <Input readOnly fullWidth value={props.input.value} error={props.meta.touched && !!props.meta.error}/>}
+        {...props} />;
diff --git a/src/views/run-process-panel/inputs/float-input.tsx b/src/views/run-process-panel/inputs/float-input.tsx
new file mode 100644 (file)
index 0000000..aeaf6cd
--- /dev/null
@@ -0,0 +1,54 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { FloatCommandInputParameter, isRequiredInput } from '~/models/workflow';
+import { Field } from 'redux-form';
+import { isNumber } from '~/validators/is-number';
+import { GenericInput } from '~/views/run-process-panel/inputs/generic-input';
+import { Input as MaterialInput } from '@material-ui/core';
+import { GenericInputProps } from './generic-input';
+export interface FloatInputProps {
+    input: FloatCommandInputParameter;
+}
+export const FloatInput = ({ input }: FloatInputProps) =>
+    <Field
+        name={input.id}
+        commandInput={input}
+        component={FloatInputComponent}
+        parse={parseFloat}
+        format={value => isNaN(value) ? '' : JSON.stringify(value)}
+        validate={[
+            isRequiredInput(input)
+                ? isNumber
+                : () => undefined,]} />;
+
+class FloatInputComponent extends React.Component<GenericInputProps> {
+    state = {
+        endsWithDecimalSeparator: false,
+    };
+
+    handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
+        const [base, fraction] = event.target.value.split('.');
+        this.setState({ endsWithDecimalSeparator: fraction === '' });
+        this.props.input.onChange(event);
+    }
+
+    render() {
+        const props = {
+            ...this.props,
+            input: {
+                ...this.props.input,
+                value: this.props.input.value + (this.state.endsWithDecimalSeparator ? '.' : ''),
+                onChange: this.handleChange,
+            },
+        };
+        return <GenericInput
+            component={Input}
+            {...props} />;
+    }
+}
+
+const Input = (props: GenericInputProps) =>
+    <MaterialInput fullWidth {...props.input} error={props.meta.touched && !!props.meta.error} />;
diff --git a/src/views/run-process-panel/inputs/generic-input.tsx b/src/views/run-process-panel/inputs/generic-input.tsx
new file mode 100644 (file)
index 0000000..a449c65
--- /dev/null
@@ -0,0 +1,34 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { WrappedFieldProps } from 'redux-form';
+import { FormGroup, FormLabel, Input, FormHelperText, FormControl } from '@material-ui/core';
+import { GenericCommandInputParameter, getInputLabel, isRequiredInput } from '../../../models/workflow';
+
+export type GenericInputProps = WrappedFieldProps & {
+    commandInput: GenericCommandInputParameter<any, any>;
+};
+
+type GenericInputContainerProps = GenericInputProps & {
+    component: React.ComponentType<GenericInputProps>;
+};
+export const GenericInput = ({ component: Component, ...props }: GenericInputContainerProps) => {
+    return <FormGroup>
+        <FormLabel
+            focused={props.meta.active}
+            required={isRequiredInput(props.commandInput)}
+            error={props.meta.touched && !!props.meta.error}>
+            {getInputLabel(props.commandInput)}
+        </FormLabel>
+        <Component {...props} />
+        <FormHelperText error={props.meta.touched && !!props.meta.error}>
+            {
+                props.meta.touched && props.meta.error
+                    ? props.meta.error
+                    : props.commandInput.doc
+            }
+        </FormHelperText>
+    </FormGroup>;
+};
\ No newline at end of file
diff --git a/src/views/run-process-panel/inputs/int-input.tsx b/src/views/run-process-panel/inputs/int-input.tsx
new file mode 100644 (file)
index 0000000..193de26
--- /dev/null
@@ -0,0 +1,36 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { IntCommandInputParameter, getInputLabel, isRequiredInput } from '~/models/workflow';
+import { Field } from 'redux-form';
+import { isInteger } from '~/validators/is-integer';
+import { GenericInputProps, GenericInput } from '~/views/run-process-panel/inputs/generic-input';
+import { Input as MaterialInput } from '@material-ui/core';
+
+export interface IntInputProps {
+    input: IntCommandInputParameter;
+}
+export const IntInput = ({ input }: IntInputProps) =>
+    <Field
+        name={input.id}
+        commandInput={input}
+        component={IntInputComponent}
+        parse={value => parseInt(value, 10)}
+        format={value => isNaN(value) ? '' : JSON.stringify(value)}
+        validate={[
+            isRequiredInput(input)
+                ? isInteger
+                : () => undefined,
+        ]} />;
+
+const IntInputComponent = (props: GenericInputProps) =>
+    <GenericInput
+        component={Input}
+        {...props} />;
+
+
+const Input = (props: GenericInputProps) =>
+    <MaterialInput fullWidth type='number' {...props.input} error={props.meta.touched && !!props.meta.error} />;
+
diff --git a/src/views/run-process-panel/inputs/string-input.tsx b/src/views/run-process-panel/inputs/string-input.tsx
new file mode 100644 (file)
index 0000000..7f02e1d
--- /dev/null
@@ -0,0 +1,32 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { isRequiredInput, StringCommandInputParameter } from '~/models/workflow';
+import { Field } from 'redux-form';
+import { require } from '~/validators/require';
+import { GenericInputProps, GenericInput } from '~/views/run-process-panel/inputs/generic-input';
+import { Input as MaterialInput } from '@material-ui/core';
+
+export interface StringInputProps {
+    input: StringCommandInputParameter;
+}
+export const StringInput = ({ input }: StringInputProps) =>
+    <Field
+        name={input.id}
+        commandInput={input}
+        component={StringInputComponent}
+        validate={[
+            isRequiredInput(input)
+                ? require
+                : () => undefined,
+        ]} />;
+
+const StringInputComponent = (props: GenericInputProps) =>
+    <GenericInput
+        component={Input}
+        {...props} />;
+
+const Input = (props: GenericInputProps) =>
+    <MaterialInput fullWidth {...props.input} error={props.meta.touched && !!props.meta.error} />;
\ No newline at end of file
diff --git a/src/views/run-process-panel/run-process-advanced-form.tsx b/src/views/run-process-panel/run-process-advanced-form.tsx
new file mode 100644 (file)
index 0000000..19beab6
--- /dev/null
@@ -0,0 +1,45 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { ExpansionPanel, ExpansionPanelDetails, ExpansionPanelSummary } from '@material-ui/core';
+import { reduxForm, Field } from 'redux-form';
+import { Grid } from '@material-ui/core';
+import { TextField } from '~/components/text-field/text-field';
+import { ExpandIcon } from '~/components/icon/icon';
+
+export const RUN_PROCESS_ADVANCED_FORM = 'runProcessAdvancedForm';
+
+export interface RunProcessAdvancedFormData {
+    output: string;
+    runtime: string;
+}
+
+export const RunProcessAdvancedForm =
+    reduxForm<RunProcessAdvancedFormData>({
+        form: RUN_PROCESS_ADVANCED_FORM
+    })(() =>
+        <form>
+            <ExpansionPanel elevation={0}>
+                <ExpansionPanelSummary style={{ padding: 0 }} expandIcon={<ExpandIcon />}>
+                    Advanced
+                </ExpansionPanelSummary>
+                <ExpansionPanelDetails style={{ padding: 0 }}>
+                    <Grid container spacing={32}>
+                        <Grid item xs={12} md={6}>
+                            <Field
+                                name='output'
+                                component={TextField}
+                                label="Output name" />
+                        </Grid>
+                        <Grid item xs={12} md={6}>
+                            <Field
+                                name='runtime'
+                                component={TextField}
+                                label="Runtime limit (hh)" />
+                        </Grid>
+                    </Grid>
+                </ExpansionPanelDetails>
+            </ExpansionPanel>
+        </form >);
diff --git a/src/views/run-process-panel/run-process-basic-form.tsx b/src/views/run-process-panel/run-process-basic-form.tsx
new file mode 100644 (file)
index 0000000..2739824
--- /dev/null
@@ -0,0 +1,38 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { reduxForm, Field } from 'redux-form';
+import { Grid } from '@material-ui/core';
+import { TextField } from '~/components/text-field/text-field';
+import { PROCESS_NAME_VALIDATION } from '~/validators/validators';
+
+export const RUN_PROCESS_BASIC_FORM = 'runProcessBasicForm';
+
+export interface RunProcessBasicFormData {
+    name: string;
+    description: string;
+}
+export const RunProcessBasicForm =
+    reduxForm<RunProcessBasicFormData>({
+        form: RUN_PROCESS_BASIC_FORM
+    })(() =>
+        <form>
+            <Grid container spacing={32}>
+                <Grid item xs={12} md={6}>
+                    <Field
+                        name='name'
+                        component={TextField}
+                        label="Enter a new name for run process"
+                        required
+                        validate={PROCESS_NAME_VALIDATION} />
+                </Grid>
+                <Grid item xs={12} md={6}>
+                    <Field
+                        name='description'
+                        component={TextField}
+                        label="Enter a description for run process" />
+                </Grid>
+            </Grid>
+        </form>);
diff --git a/src/views/run-process-panel/run-process-inputs-form.tsx b/src/views/run-process-panel/run-process-inputs-form.tsx
new file mode 100644 (file)
index 0000000..b0d5d5f
--- /dev/null
@@ -0,0 +1,83 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { reduxForm, InjectedFormProps } from 'redux-form';
+import { CommandInputParameter, CWLType, IntCommandInputParameter, BooleanCommandInputParameter, FileCommandInputParameter } from '~/models/workflow';
+import { IntInput } from '~/views/run-process-panel/inputs/int-input';
+import { StringInput } from '~/views/run-process-panel/inputs/string-input';
+import { StringCommandInputParameter, FloatCommandInputParameter, isPrimitiveOfType, File, Directory, WorkflowInputsData, EnumCommandInputParameter } from '../../models/workflow';
+import { FloatInput } from '~/views/run-process-panel/inputs/float-input';
+import { BooleanInput } from './inputs/boolean-input';
+import { FileInput } from './inputs/file-input';
+import { connect } from 'react-redux';
+import { compose } from 'redux';
+import { Grid, StyleRulesCallback, withStyles, WithStyles } from '@material-ui/core';
+import { EnumInput } from './inputs/enum-input';
+
+export const RUN_PROCESS_INPUTS_FORM = 'runProcessInputsForm';
+
+export interface RunProcessInputFormProps {
+    inputs: CommandInputParameter[];
+}
+
+export const RunProcessInputsForm = compose(
+    connect((_: any, props: RunProcessInputFormProps) => ({
+        initialValues: props.inputs.reduce(
+            (values, input) => ({ ...values, [input.id]: input.default }),
+            {}),
+    })),
+    reduxForm<WorkflowInputsData, RunProcessInputFormProps>({
+        form: RUN_PROCESS_INPUTS_FORM
+    }))(
+        (props: InjectedFormProps & RunProcessInputFormProps) =>
+            <form>
+                <Grid container spacing={32}>
+                    {props.inputs.map(input =>
+                        <InputItem input={input} key={input.id} />)}
+                </Grid>
+            </form>);
+
+type CssRules = 'inputItem';
+
+const styles: StyleRulesCallback<CssRules> = theme => ({
+    inputItem: {
+        marginBottom: theme.spacing.unit * 2,
+    }
+});
+
+const InputItem = withStyles(styles)(
+    (props: WithStyles<CssRules> & { input: CommandInputParameter }) =>
+        <Grid item xs={12} md={6} className={props.classes.inputItem}>
+            {getInputComponent(props.input)}
+        </Grid>);
+
+const getInputComponent = (input: CommandInputParameter) => {
+    switch (true) {
+        case isPrimitiveOfType(input, CWLType.BOOLEAN):
+            return <BooleanInput input={input as BooleanCommandInputParameter} />;
+
+        case isPrimitiveOfType(input, CWLType.INT):
+        case isPrimitiveOfType(input, CWLType.LONG):
+            return <IntInput input={input as IntCommandInputParameter} />;
+
+        case isPrimitiveOfType(input, CWLType.FLOAT):
+        case isPrimitiveOfType(input, CWLType.DOUBLE):
+            return <FloatInput input={input as FloatCommandInputParameter} />;
+
+        case isPrimitiveOfType(input, CWLType.STRING):
+            return <StringInput input={input as StringCommandInputParameter} />;
+
+        case isPrimitiveOfType(input, CWLType.FILE):
+            return <FileInput input={input as FileCommandInputParameter} />;
+
+        case typeof input.type === 'object' &&
+            !(input.type instanceof Array) &&
+            input.type.type === 'enum':
+            return <EnumInput input={input as EnumCommandInputParameter} />;
+
+        default:
+            return null;
+    }
+};
index b656ba1f0d776d819c2e292f0c37f35af622161a..1489527530fa427ea9e72f72a7ec316e7d41416a 100644 (file)
@@ -5,22 +5,24 @@
 import * as React from 'react';
 import { Stepper, Step, StepLabel, StepContent } from '@material-ui/core';
 import { RunProcessFirstStepDataProps, RunProcessFirstStepActionProps, RunProcessFirstStep } from '~/views/run-process-panel/run-process-first-step';
-import { RunProcessSecondStepDataProps, RunProcessSecondStepActionProps, RunProcessSecondStepForm } from '~/views/run-process-panel/run-process-second-step';
+import { RunProcessSecondStepForm } from './run-process-second-step';
 
 export type RunProcessPanelRootDataProps = {
     currentStep: number;
-} & RunProcessFirstStepDataProps & RunProcessSecondStepDataProps;
+} & RunProcessFirstStepDataProps;
 
-export type RunProcessPanelRootActionProps = RunProcessFirstStepActionProps & RunProcessSecondStepActionProps;
+export type RunProcessPanelRootActionProps = RunProcessFirstStepActionProps & {
+    runProcess: () => void;
+};
 
 type RunProcessPanelRootProps = RunProcessPanelRootDataProps & RunProcessPanelRootActionProps;
 
-export const RunProcessPanelRoot = ({ currentStep, onSearch, onSetStep, onRunProcess, onSetWorkflow, workflows, selectedWorkflow }: RunProcessPanelRootProps) =>
+export const RunProcessPanelRoot = ({ runProcess, currentStep, onSearch, onSetStep, onSetWorkflow, workflows, selectedWorkflow }: RunProcessPanelRootProps) =>
     <Stepper activeStep={currentStep} orientation="vertical" elevation={2}>
         <Step>
             <StepLabel>Choose a workflow</StepLabel>
             <StepContent>
-                <RunProcessFirstStep 
+                <RunProcessFirstStep
                     workflows={workflows}
                     selectedWorkflow={selectedWorkflow}
                     onSearch={onSearch}
@@ -31,8 +33,9 @@ export const RunProcessPanelRoot = ({ currentStep, onSearch, onSetStep, onRunPro
         <Step>
             <StepLabel>Select inputs</StepLabel>
             <StepContent>
-                <RunProcessSecondStepForm />
-                {/* <RunProcessSecondStep onSetStep={onSetStep} onRunProcess={onRunProcess} /> */}
+                <RunProcessSecondStepForm
+                    goBack={() => onSetStep(0)}
+                    runProcess={runProcess} />
             </StepContent>
         </Step>
     </Stepper>;
\ No newline at end of file
index 1f43e05ce367fd66a1718c038bf85d636d227a54..42324ab032455c023ddd0c752895f24ec63a8c97 100644 (file)
@@ -6,7 +6,7 @@ import { Dispatch } from 'redux';
 import { connect } from 'react-redux';
 import { RootState } from '~/store/store';
 import { RunProcessPanelRootDataProps, RunProcessPanelRootActionProps, RunProcessPanelRoot } from '~/views/run-process-panel/run-process-panel-root';
-import { goToStep, setWorkflow, searchWorkflows } from '~/store/run-process-panel/run-process-panel-actions';
+import { goToStep, setWorkflow, runProcess, searchWorkflows } from '~/store/run-process-panel/run-process-panel-actions';
 import { WorkflowResource } from '~/models/workflow';
 
 const mapStateToProps = ({ runProcessPanel }: RootState): RunProcessPanelRootDataProps => {
@@ -24,8 +24,8 @@ const mapDispatchToProps = (dispatch: Dispatch): RunProcessPanelRootActionProps
     onSetWorkflow: (workflow: WorkflowResource) => {
         dispatch<any>(setWorkflow(workflow));
     },
-    onRunProcess: () => {
-        
+    runProcess: () => {
+        dispatch<any>(runProcess);
     },
     onSearch: (term: string) => {
         dispatch<any>(searchWorkflows(term));
index 76ebea3e4317afe3ef5c241d1bbf38380983ac45..2585136e6ea20a86fb86d3584a471ddf98ede586 100644 (file)
@@ -3,60 +3,47 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
-import { withStyles, WithStyles, StyleRulesCallback, Grid, Button } from '@material-ui/core';
-import { ArvadosTheme } from '~/common/custom-theme';
-import { Field, reduxForm, InjectedFormProps } from 'redux-form';
-import { TextField } from '~/components/text-field/text-field';
-import { RunProcessSecondStepDataFormProps, RUN_PROCESS_SECOND_STEP_FORM_NAME } from '~/store/run-process-panel/run-process-panel-actions';
-
-type CssRules = 'root';
-
-const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
-    root: {
-
-    }
-});
-
-export interface RunProcessSecondStepDataProps {
-
+import { Grid, Button } from '@material-ui/core';
+import { RunProcessBasicForm, RUN_PROCESS_BASIC_FORM } from './run-process-basic-form';
+import { RunProcessInputsForm } from '~/views/run-process-panel/run-process-inputs-form';
+import { CommandInputParameter } from '~/models/workflow';
+import { connect } from 'react-redux';
+import { RootState } from '~/store/store';
+import { isValid } from 'redux-form';
+import { RUN_PROCESS_INPUTS_FORM } from './run-process-inputs-form';
+import { RunProcessAdvancedForm } from './run-process-advanced-form';
+
+export interface RunProcessSecondStepFormDataProps {
+    inputs: CommandInputParameter[];
+    valid: boolean;
 }
 
-export interface RunProcessSecondStepActionProps {
-    onSetStep: (step: number) => void;
-    onRunProcess: (data: RunProcessSecondStepDataFormProps) => void;
+export interface RunProcessSecondStepFormActionProps {
+    goBack: () => void;
+    runProcess: () => void;
 }
 
-type RunProcessSecondStepProps = RunProcessSecondStepDataProps
-    & RunProcessSecondStepActionProps
-    & WithStyles<CssRules>
-    & InjectedFormProps<RunProcessSecondStepDataFormProps>;
+const mapStateToProps = (state: RootState): RunProcessSecondStepFormDataProps => ({
+    inputs: state.runProcessPanel.inputs,
+    valid: isValid(RUN_PROCESS_BASIC_FORM)(state) &&
+        isValid(RUN_PROCESS_INPUTS_FORM)(state),
+});
 
-const RunProcessSecondStep = withStyles(styles)(
-    ({ onSetStep, classes }: RunProcessSecondStepProps) =>
+export type RunProcessSecondStepFormProps = RunProcessSecondStepFormDataProps & RunProcessSecondStepFormActionProps;
+export const RunProcessSecondStepForm = connect(mapStateToProps)(
+    ({ inputs, valid, goBack, runProcess }: RunProcessSecondStepFormProps) =>
         <Grid container spacing={16}>
             <Grid item xs={12}>
-                <form>
-                    <Field
-                        name='name'
-                        component={TextField}
-                        label="Enter a new name for run process" />
-                    <Field
-                        name='description'
-                        component={TextField}
-                        label="Enter a description for run process" />
-                </form>
+                <RunProcessBasicForm />
+                <RunProcessInputsForm inputs={inputs} />
+                <RunProcessAdvancedForm />
             </Grid>
             <Grid item xs={12}>
-                <Button color="primary" onClick={() => onSetStep(0)}>
+                <Button color="primary" onClick={goBack}>
                     Back
                 </Button>
-                <Button variant="contained" color="primary">
+                <Button disabled={!valid} variant="contained" color="primary" onClick={runProcess}>
                     Run Process
                 </Button>
             </Grid>
-        </Grid>
-);
-
-export const RunProcessSecondStepForm = reduxForm<RunProcessSecondStepDataFormProps>({
-    form: RUN_PROCESS_SECOND_STEP_FORM_NAME
-})(RunProcessSecondStep);
\ No newline at end of file
+        </Grid>);
index 72af6bc2f5da40cc98e6b6560bc921ccc0e48efe..a98356403bfd50ffd346c7b01c2d68c0dfd6de26 100644 (file)
@@ -3,11 +3,11 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
-import { StyleRulesCallback, WithStyles, withStyles, CardContent, Tab, Tabs, Paper } from '@material-ui/core';
+import { StyleRulesCallback, WithStyles, withStyles, CardContent, Tab, Tabs, Typography, List, ListItem, Table, TableHead, TableCell, TableBody, TableRow } 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';
+import { WorkflowResource, parseWorkflowDefinition, getWorkflowInputs, getInputLabel, stringifyInputType } from '~/models/workflow';
 
 export type CssRules = 'root' | 'tab';
 
@@ -48,20 +48,49 @@ export const WorkflowDetailsCard = withStyles(styles)(
                     {workflow ? (
                         workflow.description
                     ) : (
-                        <DataTableDefaultView
-                            icon={WorkflowIcon}
-                            messages={['Please select a workflow to see its description.']} />
-                    )}
+                            <DataTableDefaultView
+                                icon={WorkflowIcon}
+                                messages={['Please select a workflow to see its description.']} />
+                        )}
                 </CardContent>}
                 {value === 1 && <CardContent>
-                    {workflow ? (
-                        workflow.name
-                    ) : (
-                        <DataTableDefaultView
+                    {workflow
+                        ? this.renderInputsTable()
+                        : <DataTableDefaultView
                             icon={WorkflowIcon}
                             messages={['Please select a workflow to see its inputs.']} />
-                    )}
+                    }
                 </CardContent>}
             </div>;
         }
+
+        get inputs() {
+            if (this.props.workflow) {
+                const definition = parseWorkflowDefinition(this.props.workflow);
+                if (definition) {
+                    return getWorkflowInputs(definition);
+                }
+            }
+            return;
+        }
+
+        renderInputsTable() {
+            return <Table>
+                <TableHead>
+                    <TableRow>
+                        <TableCell>Label</TableCell>
+                        <TableCell>Type</TableCell>
+                        <TableCell>Description</TableCell>
+                    </TableRow>
+                </TableHead>
+                <TableBody>
+                    {this.inputs && this.inputs.map(input =>
+                        <TableRow key={input.id}>
+                            <TableCell>{getInputLabel(input)}</TableCell>
+                            <TableCell>{stringifyInputType(input)}</TableCell>
+                            <TableCell>{input.doc}</TableCell>
+                        </TableRow>)}
+                </TableBody>
+            </Table>;
+        }
     });
\ No newline at end of file
index 8d21f33588be7c6d85fdbde995064e13c2c21127..57654bc85b12aa0b4ea0abf15aec3a09d9cd524d 100644 (file)
@@ -6,7 +6,7 @@ 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 { WORKFLOW_PANEL_ID, workflowPanelActions } from '~/store/workflow-panel/workflow-panel-actions';
 import {
     ResourceLastModifiedDate,
     RosurceWorkflowName,
@@ -18,6 +18,7 @@ import { DataColumns } from '~/components/data-table/data-table';
 import { DataTableFilterItem } from '~/components/data-table-filters/data-table-filters';
 import { Grid, Paper } from '@material-ui/core';
 import { WorkflowDetailsCard } from './workflow-description-card';
+import { WorkflowResource } from '../../models/workflow';
 
 export enum WorkflowPanelColumnNames {
     NAME = "Name",
@@ -30,11 +31,17 @@ export interface WorkflowPanelFilter extends DataTableFilterItem {
     type: ResourceStatus;
 }
 
-interface WorkflowPanelDataProps {
-    handleRowDoubleClick: any;
-    handleRowClick: any;
+export interface WorkflowPanelDataProps {
+    workflow?: WorkflowResource;
 }
 
+export interface WorfklowPanelActionProps {
+    handleRowDoubleClick: (workflowUuid: string) => void;
+    handleRowClick: (workflowUuid: string) => void;
+}
+
+export type WorkflowPanelProps = WorkflowPanelDataProps & WorfklowPanelActionProps;
+
 export enum ResourceStatus {
     PUBLIC = "Public",
     PRIVATE = "Private",
@@ -103,7 +110,7 @@ export const workflowPanelColumns: DataColumns<string, WorkflowPanelFilter> = [
     }
 ];
 
-export const WorkflowPanelView = ({ ...props }) => {
+export const WorkflowPanelView = (props: WorkflowPanelProps) => {
     return <Grid container spacing={16}>
         <Grid item xs={6}>
             <DataExplorer
@@ -116,7 +123,7 @@ export const WorkflowPanelView = ({ ...props }) => {
         </Grid>
         <Grid item xs={6}>
             <Paper style={{ height: '100%' }}>
-                <WorkflowDetailsCard />
+                <WorkflowDetailsCard workflow={props.workflow} />
             </Paper>
         </Grid>
     </Grid>;
index 279097d8e215cc14a9573596afb67056e0b5c686..0dbf918e2f36f4dbff174b94380045d557fac169 100644 (file)
@@ -2,23 +2,27 @@
 //
 // 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';
+import { WorfklowPanelActionProps, WorkflowPanelDataProps } from './workflow-panel-view';
+import { showWorkflowDetails, getWorkflowDetails } from '../../store/workflow-panel/workflow-panel-actions';
+import { RootState } from '~/store/store';
 
-const mapDispatchToProps = (dispatch: Dispatch) => ({
+const mapStateToProps = (state: RootState): WorkflowPanelDataProps => ({ 
+    workflow: getWorkflowDetails(state)
+});
+
+const mapDispatchToProps = (dispatch: Dispatch): WorfklowPanelActionProps => ({
 
     handleRowDoubleClick: (uuid: string) => {
         dispatch<any>(navigateTo(uuid));
     },
-    
+
     handleRowClick: (uuid: string) => {
-        dispatch(loadDetailsPanel(uuid));
+        dispatch(showWorkflowDetails(uuid));
     }
 });
 
-export const WorkflowPanel= connect(undefined, mapDispatchToProps)(
-    (props) => <WorkflowPanelView {...props}/>);
\ No newline at end of file
+export const WorkflowPanel = connect(mapStateToProps, mapDispatchToProps)(WorkflowPanelView);
\ No newline at end of file
index d9676b39ed291f7be003c943257e0899413aa546..32c9d0e8caae560dd4d975b5ae9305e12e1f6506 100644 (file)
--- a/yarn.lock
+++ b/yarn.lock
   version "0.0.29"
   resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
 
+"@types/js-yaml@3.11.2":
+  version "3.11.2"
+  resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.11.2.tgz#699ad86054cc20043c30d66a6fcde30bbf5d3d5e"
+
 "@types/jss@^9.5.3":
   version "9.5.6"
   resolved "https://registry.yarnpkg.com/@types/jss/-/jss-9.5.6.tgz#96e1d246ddfbccc4867494077c714773cf29acde"
@@ -4395,7 +4399,7 @@ js-tokens@^3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
 
-js-yaml@^3.4.3, js-yaml@^3.7.0:
+js-yaml@3.12.0, js-yaml@^3.4.3, js-yaml@^3.7.0:
   version "3.12.0"
   resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1"
   dependencies: