# 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
"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",
"@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",
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';
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} />;
},
});
-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}
autoComplete='off'
autoFocus={props.autoFocus}
fullWidth={true}
+ required={props.required}
{...props.input}
/>);
--- /dev/null
+// 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;
+};
--- /dev/null
+// 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
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);
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,
+ });
+};
requestingContainerUuid: string | null;
containerUuid: string | null;
containerCountMax: number;
- mounts: MountType[];
+ mounts: {[path: string]: MountType};
runtimeConstraints: RuntimeConstraints;
schedulingParameters: SchedulingParameters;
containerImage: string;
TEMPORARY_DIRECTORY = 'tmp',
KEEP = 'keep',
MOUNTED_FILE = 'file',
- JSON = 'JSON'
+ JSON = 'json'
}
export type MountType =
GitTreeMount |
TemporaryDirectoryMount |
KeepMount |
- JSONMount;
+ JSONMount |
+ FileMount;
export interface CollectionMount {
kind: MountKind.COLLECTION;
export interface JSONMount {
kind: MountKind.JSON;
- content: string;
+ content: any;
+}
+
+export interface FileMount {
+ kind: MountKind.MOUNTED_FILE;
+ path: string;
}
// 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,
+ }
+ };
+};
GROUP = 'j7d0g',
LOG = '57u5n',
USER = 'tpzed',
- WORKFLOW = '7fd4e'
+ WORKFLOW = '7fd4e',
}
export const RESOURCE_UUID_PATTERN = '.{5}-.{5}-.{15}';
export interface RuntimeConstraints {
ram: number;
vcpus: number;
- keepCacheRam: number;
+ keepCacheRam?: number;
API: boolean;
}
// 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';
+ }
+};
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)),
//
// 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';
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);
+ }
}
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>;
tagService,
userService,
webdavClient,
- workflowService
+ workflowService,
};
};
// 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) {
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>(),
}
};
-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);
// 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
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 = () =>
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;
+};
--- /dev/null
+// 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;
+};
--- /dev/null
+// 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;
+};
--- /dev/null
+// 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;
+};
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';
};
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}
</MenuItem>
</Menu>
</Grid>
- </Grid> }
+ </Grid>}
</Toolbar>;
}
}
handleRunProcessClick = () => {
+ this.props.dispatch(runProcessPanelActions.SET_PROCESS_OWNER_UUID(this.props.currentItemId));
this.props.dispatch<any>(navigateToRunProcess);
}
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+// 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} />;
--- /dev/null
+// 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} />;
--- /dev/null
+// 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
--- /dev/null
+// 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} />;
+
--- /dev/null
+// 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
--- /dev/null
+// 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 >);
--- /dev/null
+// 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>);
--- /dev/null
+// 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;
+ }
+};
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}
<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
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 => {
onSetWorkflow: (workflow: WorkflowResource) => {
dispatch<any>(setWorkflow(workflow));
},
- onRunProcess: () => {
-
+ runProcess: () => {
+ dispatch<any>(runProcess);
},
onSearch: (term: string) => {
dispatch<any>(searchWorkflows(term));
// 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>);
// 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';
{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
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,
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",
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",
}
];
-export const WorkflowPanelView = ({ ...props }) => {
+export const WorkflowPanelView = (props: WorkflowPanelProps) => {
return <Grid container spacing={16}>
<Grid item xs={6}>
<DataExplorer
</Grid>
<Grid item xs={6}>
<Paper style={{ height: '100%' }}>
- <WorkflowDetailsCard />
+ <WorkflowDetailsCard workflow={props.workflow} />
</Paper>
</Grid>
</Grid>;
//
// 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
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"
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: