IF YOU WOULD LIKE TO GET AN ACCOUNT, please write an email to s dot adaszewski at gmail dot com. User accounts are meant only to report issues and/or generate pull requests. This is a purpose-specific Git hosting for ADARED projects. Thank you for your understanding!
Browse Source

Further modularization of wb-launch-workflow-page, looks much more manageable now.

pull/1/head
parent
commit
f6ffec6bb1
4 changed files with 190 additions and 149 deletions
  1. +87
    -0
      frontend/src/js/arvados/process/wb-submit-container-request.js
  2. +1
    -1
      frontend/src/js/arvados/process/wb-uuids-to-cwl.js
  3. +71
    -0
      frontend/src/js/component/wb-workflow-input.js
  4. +31
    -148
      frontend/src/js/page/wb-launch-workflow-page.js

+ 87
- 0
frontend/src/js/arvados/process/wb-submit-container-request.js View File

@@ -0,0 +1,87 @@
import makeArvadosRequest from 'make-arvados-request';
import wbUuidsToCwl from 'wb-uuids-to-cwl';
function wbParseWorkflowInputs(workflowDefinition, userInputs, errors) {
// first see if all inputs are parseable
const inputs = {};
const main = workflowDefinition['$graph'].find(a => (a.id === '#main'));
for (let k in userInputs) {
try {
let val = jsyaml.safeLoad(userInputs[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);
}
}
return inputs;
}
// params:
// arvHost, arvToken, inputs,
// projectUuid, workflowDefinition, workflowUuid
// processName, processDescription
function wbSubmitContainerRequest(params) {
const { workflowDefinition, workflowUuid,
processName, processDescription, inputs,
arvHost, arvToken, projectUuid } = params;
// prepare a request
const req = {
name: processName,
description: processDescription,
owner_uuid: projectUuid,
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,
'--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 prom = makeArvadosRequest(arvHost, arvToken,
'/arvados/v1/container_requests',
{ method: 'POST', data: JSON.stringify(req) });
return prom;
}
export { wbParseWorkflowInputs, wbSubmitContainerRequest };

+ 1
- 1
frontend/src/js/arvados/process/wb-uuids-to-cwl.js View File

@@ -2,7 +2,7 @@ function wbUuidsToCwl(obj) {
if (obj instanceof Array) {
const res = [];
for (let k in obj) {
res[k] = uuidsToCwl(obj[k]);
res[k] = wbUuidsToCwl(obj[k]);
}
return res;
}


+ 71
- 0
frontend/src/js/component/wb-workflow-input.js View File

@@ -0,0 +1,71 @@
import { h, Component } from 'preact';
import wbInputSpecInfo from 'wb-input-spec-info';
import WBPathDisplay from 'wb-path-display';
import { parseKeepRef } from 'wb-process-misc';
class WBWorkflowInput extends Component {
render({ app, inputSpec, inputsDict, browseDialogRef }) {
const { isFile, isDirectory, isArray } = wbInputSpecInfo(inputSpec);
if (!isFile && !isDirectory)
return (
<div>
<input class="form-control w-100" type="text" placeholder={ inputSpec.label }
value={ inputsDict[inputSpec.id] }
onchange={ e => (inputsDict[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();
browseDialogRef.current.show(
[].concat(isFile ? 'file' : []).concat(isDirectory ? 'directory' : []),
isArray,
v => {
inputsDict[inputSpec.id] = JSON.stringify(v);
this.setState({});
});
} }>
Browse...
</button>
);
let value = inputsDict[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={ inputsDict[inputSpec.id] }
onchange={ e => (inputsDict[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>
);
}
}
export default WBWorkflowInput;

+ 31
- 148
frontend/src/js/page/wb-launch-workflow-page.js View File

@@ -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>
);


Loading…
Cancel
Save