# 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",
--- /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);
};
+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;
}
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 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';
+ }
+};
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 { 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;
+};
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 } from '~/models/workflow';
+import { WorkflowInput } from '~/components/workflow-inputs-form/workflow-input';
export type CssRules = 'root' | 'tab';
}
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}>
<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 => <WorkflowInput key={input.id} input={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
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 } 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
dataTableDefaultView={<DataTableDefaultView icon={WorkflowIcon} />} />
</Grid>
<Grid item xs={6}>
- <WorkflowDetailsCard />
+ <WorkflowDetailsCard workflow={props.workflow} />
</Grid>
</Grid>;
};
\ No newline at end of file
//
// 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
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
+"@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"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls=
-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"
integrity sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==