diff --git a/frontend/src/js/misc/wb-disable-controls.js b/frontend/src/js/misc/wb-disable-controls.js new file mode 100644 index 0000000..251d260 --- /dev/null +++ b/frontend/src/js/misc/wb-disable-controls.js @@ -0,0 +1,12 @@ +function wbDisableControls() { + $('input, select, button').attr('disabled', 'disabled'); + $('a').each(function() { $(this).data('old_href', $(this).attr('href')); }); + $('a').attr('href', null); +} + +function wbEnableControls() { + $('input, select, button').attr('disabled', null); + $('a').each(function() { $(this).attr('href', $(this).data('old_href')); }); +} + +export { wbEnableControls, wbDisableControls }; diff --git a/frontend/src/js/page/wb-launch-workflow-page.js b/frontend/src/js/page/wb-launch-workflow-page.js index 6b0cdfe..322ccf2 100644 --- a/frontend/src/js/page/wb-launch-workflow-page.js +++ b/frontend/src/js/page/wb-launch-workflow-page.js @@ -1,10 +1,12 @@ import { h, Component, createRef } from 'preact'; +import { route } from 'preact-router'; 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'; function parseDefinition(text) { @@ -21,6 +23,41 @@ function encodeURIComponentIncludingDots(s) { return encodeURIComponent(s).replace('.', '%2E'); } +function inputSpecInfo(inputSpec) { + const isFile = (inputSpec.type === 'File' || inputSpec.type === 'File[]' || + (inputSpec.type.type === 'array' && inputSpec.type.items === 'File')); + + const isDirectory = (inputSpec.type === 'Directory' || inputSpec.type === 'Directory[]' || + (inputSpec.type.type === 'array' && inputSpec.type.items === 'Directory')); + + const isArray = (inputSpec.type === 'File[]' || inputSpec.type === 'Directory[]' || + inputSpec.type.type === 'array'); + + return { isFile, isDirectory, isArray }; +} + +function uuidsToCwl(obj, isFile) { + if (obj instanceof Array) { + const res = {}; + for (let k in obj) { + res[k] = uuidsToCwl(obj[k], isFile); + } + return res; + } + + if (typeof(obj) === 'string' && + (/^[0-9a-z]{5}-[0-9a-z]{5}-[0-9a-z]{15}/.exec(obj) || + /^[0-9a-f]{32}\+[0-9]+/.exec(obj))) { + + return { + 'class': (isFile ? 'File' : 'Directory'), + 'location': 'keep:' + obj + }; + } + + throw Error('Expected Arvados path or array of paths'); +} + class WBPathDisplay extends Component { fetchData() { const { app, path } = this.props; @@ -67,6 +104,7 @@ class WBLaunchWorkflowPage extends Component { super(...args); this.browseDialogRef = createRef(); this.state.inputs = {}; + this.state.errors = []; } componentDidMount() { @@ -86,14 +124,7 @@ class WBLaunchWorkflowPage extends Component { renderInput(inputSpec) { const { app } = this.props; - const isFile = (inputSpec.type === 'File' || inputSpec.type === 'File[]' || - (inputSpec.type.type === 'array' && inputSpec.type.items === 'File')); - - const isDirectory = (inputSpec.type === 'Directory' || inputSpec.type === 'Directory[]' || - (inputSpec.type.type === 'array' && inputSpec.type.items === 'Directory')); - - const isArray = true; // (inputSpec.type === 'File[]' || inputSpec.type === 'Directory[]' || - // inputSpec.type.type === 'array'); + const { isFile, isDirectory, isArray } = inputSpecInfo(inputSpec); if (!isFile && !isDirectory) return ( @@ -153,9 +184,97 @@ class WBLaunchWorkflowPage extends Component { ); } + 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]); + const { isFile } = inputSpecInfo(main.inputs.find(a => (a.id === k))); + val = uuidsToCwl(val, isFile); + k = k.split('/').slice(1).join('/'); + inputs[k] = val; + } catch (exc) { + errors.push('Error parsing ' + k + ': ' + exc.message); + } + } + + 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 + } + } + }; + + wbDisableControls(); + let prom = makeArvadosRequest(arvHost, arvToken, + '/arvados/v1/container_requests', + { method: 'POST', data: JSON.stringify(req) }); + prom = prom.then(xhr => { + wbEnableControls(); + route('/process/' + xhr.response.uuid); + }); + + // throw Error('Not implemented'); + } + render({ app, workflowUuid }, { workflow, workflowDefinition, projectUuid, processName, processDescription, - defaultProcessName, defaultProcessDescription }) { + defaultProcessName, defaultProcessDescription, errors }) { return (
@@ -221,6 +340,16 @@ class WBLaunchWorkflowPage extends Component { Submit
+ + { errors.length > 0 ? ( +
+ { errors.map(err => ( + + ))} +
+ ) : null } ) :
Loading...
} );