Add workflow definition parser
authorMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Thu, 27 Sep 2018 23:29:10 +0000 (01:29 +0200)
committerMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Thu, 27 Sep 2018 23:29:10 +0000 (01:29 +0200)
Feature #13863

Arvados-DCO-1.1-Signed-off-by: Michal Klobukowski <michal.klobukowski@contractors.roche.com>

src/index.tsx
src/models/workflow.ts
src/store/workflow-panel/workflow-panel-actions.ts
src/views/workflow-panel/workflow-description-card.tsx
src/views/workflow-panel/workflow-panel-view.tsx
src/views/workflow-panel/workflow-panel.tsx

index d64798bca541bfc3996df94fdea8c37c81ec09d0..2f8e73c4a112035804e2d9623bdc152683c518cc 100644 (file)
@@ -113,7 +113,6 @@ const initListener = (history: History, store: RootStore, services: ServiceRepos
             initWebSocket(config, services.authService, store);
             await store.dispatch(loadWorkbench());
             addRouteChangeHandlers(history, store);
-            // createSampleProcess(services);
         }
     };
 };
index d644b7909047b0c28db876f3dc1dbe9a96cd0c43..923a9cbd31efb6bcff7bae863e239664b55973c6 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 interface CommandInputParameter {
+    id: string;
+    label?: string;
+    doc?: string | string[];
+    default?: any;
+    type?: CWLType | CWLType[] | CommandInputEnumSchema | CommandInputArraySchema;
+}
+
+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 {
+    items: CWLType;
+    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 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 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 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;
+};
index 60e17b6017945bc011de6b8273659abbf6773dbe..57cf89f7ea855d129413b4160c21a0300e058030 100644 (file)
@@ -7,7 +7,8 @@ import { StyleRulesCallback, WithStyles, withStyles, CardContent, Tab, Tabs, Pap
 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, stringifyInputType } from '~/models/workflow';
+import { DetailsAttribute } from '~/components/details-attribute/details-attribute';
 
 export type CssRules = 'root' | 'tab';
 
@@ -37,7 +38,7 @@ export const WorkflowDetailsCard = withStyles(styles)(
         }
 
         render() {
-            const { classes } = this.props;
+            const { classes, workflow } = this.props;
             const { value } = this.state;
             return <Paper className={classes.root}>
                 <Tabs value={value} onChange={this.handleChange} centered={true}>
@@ -45,14 +46,29 @@ export const WorkflowDetailsCard = withStyles(styles)(
                     <Tab className={classes.tab} label="Inputs" />
                 </Tabs>
                 {value === 0 && <CardContent>
-                    Description
-                    <DataTableDefaultView
-                        icon={WorkflowIcon}
-                        messages={['Please select a workflow to see its description.']} />
+                    {workflow
+                        ? workflow.description
+                        : <DataTableDefaultView
+                            icon={WorkflowIcon}
+                            messages={['Please select a workflow to see its description.']} />}
                 </CardContent>}
                 {value === 1 && <CardContent>
-                    Inputs
+                    {workflow && this.inputs
+                        ? this.inputs.map(input => <DetailsAttribute key={input.id} label={input.label || ''} value={stringifyInputType(input)} />)
+                        : <DataTableDefaultView
+                            icon={WorkflowIcon}
+                            messages={['Please select a workflow to see its description.']} />}
                 </CardContent>}
             </Paper>;
         }
+
+        get inputs() {
+            if (this.props.workflow) {
+                const definition = parseWorkflowDefinition(this.props.workflow);
+                if (definition) {
+                    return getWorkflowInputs(definition);
+                }
+            }
+            return;
+        }
     });
\ No newline at end of file
index 8a29cb7f1647c8ab6a20547bc1f936e6a0287872..c48f46ad8cf97799552ec30a00de2b3d64c1b431 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 } 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
@@ -114,7 +121,7 @@ export const WorkflowPanelView = ({...props}) => {
                 dataTableDefaultView={<DataTableDefaultView icon={WorkflowIcon} />} />
         </Grid>
         <Grid item xs={6}>
-            <WorkflowDetailsCard />
+            <WorkflowDetailsCard workflow={props.workflow} />
         </Grid>
     </Grid>;
 };
\ No newline at end of file
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