|
|
@@ -4,15 +4,12 @@ import WBNavbarCommon from 'wb-navbar-common'; |
|
|
|
import WBArvadosCrumbs from 'wb-arvados-crumbs';
|
|
|
|
import WBBrowseDialog from 'wb-browse-dialog';
|
|
|
|
import WBTable from 'wb-table';
|
|
|
|
import WBNameAndUuid from 'wb-name-and-uuid';
|
|
|
|
import makeArvadosRequest from 'make-arvados-request';
|
|
|
|
import { wbDisableControls, wbEnableControls } from 'wb-disable-controls';
|
|
|
|
import linkState from 'linkstate';
|
|
|
|
import wbParseWorkflowDef from 'wb-parse-workflow-def';
|
|
|
|
import wbInputSpecInfo from 'wb-input-spec-info';
|
|
|
|
import wbUuidsToCwl from 'wb-uuids-to-cwl';
|
|
|
|
import { encodeURIComponentIncludingDots, parseKeepRef } from 'wb-process-misc';
|
|
|
|
import WBPathDisplay from 'wb-path-display';
|
|
|
|
import { wbParseWorkflowInputs, wbSubmitContainerRequest } from 'wb-submit-container-request';
|
|
|
|
import WBWorkflowInput from 'wb-workflow-input';
|
|
|
|
|
|
|
|
class WBLaunchWorkflowPage extends Component {
|
|
|
|
constructor(...args) {
|
|
|
@@ -43,156 +40,40 @@ class WBLaunchWorkflowPage extends Component { |
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
renderInput(inputSpec) {
|
|
|
|
const { app } = this.props;
|
|
|
|
|
|
|
|
const { isFile, isDirectory, isArray } = wbInputSpecInfo(inputSpec);
|
|
|
|
|
|
|
|
if (!isFile && !isDirectory)
|
|
|
|
return (
|
|
|
|
<div>
|
|
|
|
<input class="form-control w-100" type="text" placeholder={ inputSpec.label }
|
|
|
|
value={ this.state.inputs[inputSpec.id] }
|
|
|
|
onchange={ e => (this.state.inputs[inputSpec.id] = e.target.value) }></input>
|
|
|
|
<div class="mt-2 text-muted">{ inputSpec.doc }</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
|
|
|
|
const button = (
|
|
|
|
<button class="btn btn-outline-primary"
|
|
|
|
onclick={ e => {
|
|
|
|
e.preventDefault();
|
|
|
|
this.browseDialogRef.current.show(
|
|
|
|
[].concat(isFile ? 'file' : []).concat(isDirectory ? 'directory' : []),
|
|
|
|
isArray,
|
|
|
|
v => {
|
|
|
|
this.state.inputs[inputSpec.id] = JSON.stringify(v);
|
|
|
|
this.setState({});
|
|
|
|
});
|
|
|
|
} }>
|
|
|
|
Browse...
|
|
|
|
</button>
|
|
|
|
);
|
|
|
|
|
|
|
|
let value = this.state.inputs[inputSpec.id];
|
|
|
|
if (value) {
|
|
|
|
try {
|
|
|
|
value = jsyaml.load(value);
|
|
|
|
} catch (_) {}
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div>
|
|
|
|
<div class="input-group">
|
|
|
|
<input class="form-control w-100" type="text" placeholder={ inputSpec.label }
|
|
|
|
value={ this.state.inputs[inputSpec.id] }
|
|
|
|
onchange={ e => (this.state.inputs[inputSpec.id] = e.target.value) }></input>
|
|
|
|
<div class="input-group-append">
|
|
|
|
{ button }
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="mt-2 text-muted">{ inputSpec.doc }</div>
|
|
|
|
{ value ?
|
|
|
|
isArray ? (
|
|
|
|
<ul class="mb-0">
|
|
|
|
{ value.map(path => (
|
|
|
|
<li>
|
|
|
|
<WBPathDisplay app={ app } path={ parseKeepRef(path) } />
|
|
|
|
</li>
|
|
|
|
)) }
|
|
|
|
</ul>
|
|
|
|
) : (
|
|
|
|
<WBPathDisplay app={ app } path={ parseKeepRef(value) } />
|
|
|
|
) : null }
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
submit() {
|
|
|
|
// first see if all inputs are parseable
|
|
|
|
const inputs = {};
|
|
|
|
const errors = [];
|
|
|
|
|
|
|
|
const { workflowDefinition } = this.state;
|
|
|
|
const main = workflowDefinition['$graph'].find(a => (a.id === '#main'));
|
|
|
|
|
|
|
|
for (let k in this.state.inputs) {
|
|
|
|
try {
|
|
|
|
let val = jsyaml.safeLoad(this.state.inputs[k]);
|
|
|
|
val = wbUuidsToCwl(val);
|
|
|
|
k = k.split('/').slice(1).join('/');
|
|
|
|
inputs[k] = (val === undefined ? null : val);
|
|
|
|
} catch (exc) {
|
|
|
|
errors.push('Error parsing ' + k + ': ' + exc.message);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const { app, workflowUuid } = this.props;
|
|
|
|
const { arvHost, arvToken, currentUser } = app.state;
|
|
|
|
const { workflowDefinition, projectUuid,
|
|
|
|
processName, processDescription,
|
|
|
|
defaultProcessName, defaultProcessDescription } = this.state;
|
|
|
|
|
|
|
|
const errors = [];
|
|
|
|
const inputs = wbParseWorkflowInputs(workflowDefinition,
|
|
|
|
this.state.inputs, errors);
|
|
|
|
if (errors.length > 0) {
|
|
|
|
this.setState({ errors });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// prepare a request
|
|
|
|
const { app, workflowUuid } = this.props;
|
|
|
|
const { processName, processDescription,
|
|
|
|
defaultProcessName, defaultProcessDescription,
|
|
|
|
projectUuid } = this.state;
|
|
|
|
const { arvHost, arvToken, currentUser } = app.state;
|
|
|
|
const req = {
|
|
|
|
name: processName || defaultProcessName,
|
|
|
|
description: processDescription || defaultProcessDescription,
|
|
|
|
owner_uuid: projectUuid || currentUser.uuid,
|
|
|
|
container_image: 'arvados/jobs',
|
|
|
|
properties: {
|
|
|
|
template_uuid: workflowUuid
|
|
|
|
},
|
|
|
|
runtime_constraints: {
|
|
|
|
API: true,
|
|
|
|
vcpus: 1,
|
|
|
|
ram: 1073741824
|
|
|
|
},
|
|
|
|
cwd: '/var/spool/cwl',
|
|
|
|
command: [
|
|
|
|
'arvados-cwl-runner',
|
|
|
|
'--local',
|
|
|
|
'--api=containers',
|
|
|
|
'--project-uuid=' + (projectUuid || currentUser.uuid),
|
|
|
|
'--collection-cache-size=256',
|
|
|
|
'/var/lib/cwl/workflow.json#main',
|
|
|
|
'/var/lib/cwl/cwl.input.json'],
|
|
|
|
output_path: '/var/spool/cwl',
|
|
|
|
priority: 1,
|
|
|
|
state: 'Committed',
|
|
|
|
mounts: {
|
|
|
|
'stdout': {
|
|
|
|
kind: 'file',
|
|
|
|
path: '/var/spool/cwl/cwl.output.json'
|
|
|
|
},
|
|
|
|
'/var/spool/cwl': {
|
|
|
|
kind: 'collection',
|
|
|
|
writable: true
|
|
|
|
},
|
|
|
|
'/var/lib/cwl/workflow.json': {
|
|
|
|
kind: 'json',
|
|
|
|
content: workflowDefinition
|
|
|
|
},
|
|
|
|
'/var/lib/cwl/cwl.input.json': {
|
|
|
|
kind: 'json',
|
|
|
|
content: inputs
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
const params = {
|
|
|
|
arvHost, arvToken, inputs,
|
|
|
|
processName: processName || defaultProcessName,
|
|
|
|
processDescription: processDescription || defaultProcessDescription,
|
|
|
|
projectUuid: projectUuid || currentUser.uuid,
|
|
|
|
workflowUuid, workflowDefinition
|
|
|
|
}
|
|
|
|
|
|
|
|
wbDisableControls();
|
|
|
|
let prom = makeArvadosRequest(arvHost, arvToken,
|
|
|
|
'/arvados/v1/container_requests',
|
|
|
|
{ method: 'POST', data: JSON.stringify(req) });
|
|
|
|
let prom = wbSubmitContainerRequest(params);
|
|
|
|
prom = prom.then(xhr => {
|
|
|
|
wbEnableControls();
|
|
|
|
route('/process/' + xhr.response.uuid);
|
|
|
|
});
|
|
|
|
|
|
|
|
// throw Error('Not implemented');
|
|
|
|
prom = prom.catch(exc => {
|
|
|
|
wbEnableControls();
|
|
|
|
this.setState({ errors: [ exc.message ] });
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
render({ app, workflowUuid },
|
|
|
@@ -254,16 +135,12 @@ class WBLaunchWorkflowPage extends Component { |
|
|
|
<WBTable columns={ [ 'Name', 'Value'] }
|
|
|
|
rows={ workflowDefinition.$graph.find(a => (a.id === '#main')).inputs.map(it => [
|
|
|
|
it.label || it.id,
|
|
|
|
this.renderInput(it)
|
|
|
|
( <WBWorkflowInput app={ app } inputSpec={ it }
|
|
|
|
inputsDict={ this.state.inputs }
|
|
|
|
browseDialogRef={ this.browseDialogRef } /> )
|
|
|
|
]) } />
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="form-group">
|
|
|
|
<button class="btn btn-success" onclick={ e => { e.preventDefault(); this.submit(); } }>
|
|
|
|
Submit
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{ errors.length > 0 ? (
|
|
|
|
<div class="form-group">
|
|
|
|
{ errors.map(err => (
|
|
|
@@ -273,6 +150,12 @@ class WBLaunchWorkflowPage extends Component { |
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
) : null }
|
|
|
|
|
|
|
|
<div class="form-group">
|
|
|
|
<button class="btn btn-success" onclick={ e => { e.preventDefault(); this.submit(); } }>
|
|
|
|
Submit
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</form>) : <div>Loading...</div> }
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|