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) { let definition; try { definition = JSON.parse(text); } catch (_) { definition = jsyaml.load(text); } return definition; } function encodeURIComponentIncludingDots(s) { return encodeURIComponent(s).replace('.', '%2E'); } function inputSpecInfo(inputSpec) { const isFile = (inputSpec.type === 'File' || inputSpec.type === 'File[]' || (inputSpec.type.type === 'array' && [].concat(inputSpec.type.items).indexOf('File') !== -1)); const isDirectory = (inputSpec.type === 'Directory' || inputSpec.type === 'Directory[]' || (inputSpec.type.type === 'array' && [].concat(inputSpec.type.items).indexOf('Directory') !== -1)); const isArray = (inputSpec.type === 'File[]' || inputSpec.type === 'Directory[]' || inputSpec.type.type === 'array'); return { isFile, isDirectory, isArray }; } function uuidsToCwl(obj) { if (obj instanceof Array) { const res = []; for (let k in obj) { res[k] = uuidsToCwl(obj[k]); } 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))) { const isDirectory = obj.endsWith('/'); return { 'class': (isDirectory ? 'Directory' : 'File'), 'location': 'keep:' + (isDirectory ? obj.substr(0, obj.length - 1) : obj) }; } throw Error('Expected Arvados path or array of paths'); } function parseKeepRef(value) { if (typeof(value) === 'object' && 'location' in value && value.location.startsWith('keep:')) return value.location.substr(5); return value; } class WBPathDisplay extends Component { fetchData() { const { app } = this.props; const { arvHost, arvToken } = app.state; let { path } = this.props; if (path.endsWith('/')) path = path.substr(0, path.length - 1); let m; if (m = /^[0-9a-f]{32}\+[0-9]+/.exec(path)); else if (m = /^[a-z0-9]{5}-[a-z0-9]{5}-[a-z0-9]{15}/.exec(path)); else return; let prom = makeArvadosRequest(arvHost, arvToken, '/arvados/v1/collections/' + m[0]); prom = prom.then(xhr => this.setState({ item: xhr.response, tail: path.substr(m[0].length) })); prom = prom.catch(() => this.setState({ 'error': 'Cannot load' })); } componentDidMount() { this.fetchData(); } componentWillReceiveProps(nextProps) { this.props = nextProps; this.fetchData(); } render({}, { item, tail, error }) { if (error) return error; if (!item) return 'Loading...'; return ( { item.name || item.uuid } { tail } ); } } class WBLaunchWorkflowPage extends Component { constructor(...args) { super(...args); this.browseDialogRef = createRef(); this.state.inputs = {}; this.state.errors = []; } componentDidMount() { let { app, workflowUuid } = this.props; let { arvHost, arvToken } = app.state; let prom = makeArvadosRequest(arvHost, arvToken, '/arvados/v1/workflows/' + workflowUuid); prom = prom.then(xhr => { const def = parseDefinition(xhr.response.definition); const inputs = {}; const main = def['$graph'].find(a => (a.id === '#main')); main.inputs.map(a => (inputs[a.id] = JSON.stringify(a.default))); this.setState({ 'workflow': xhr.response, 'workflowDefinition': def, 'defaultProcessName': xhr.response.name + ' ' + (new Date().toISOString()), 'defaultProcessDescription': xhr.response.description, inputs }); }); } renderInput(inputSpec) { const { app } = this.props; const { isFile, isDirectory, isArray } = inputSpecInfo(inputSpec); if (!isFile && !isDirectory) return (
(this.state.inputs[inputSpec.id] = e.target.value) }>
{ inputSpec.doc }
); const button = ( ); let value = this.state.inputs[inputSpec.id]; if (value) { try { value = jsyaml.load(value); } catch (_) {} } return (
(this.state.inputs[inputSpec.id] = e.target.value) }>
{ button }
{ inputSpec.doc }
{ value ? isArray ? ( ) : ( ) : null }
); } 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 = uuidsToCwl(val); k = k.split('/').slice(1).join('/'); inputs[k] = (val === undefined ? null : 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, errors }) { return (
{ workflow ? (

Launch Workflow

{ projectUuid ? ( ) : null }
(a.id === '#main')).inputs.map(it => [ it.label || it.id, this.renderInput(it) ]) } />
{ errors.length > 0 ? (
{ errors.map(err => ( ))}
) : null }
) :
Loading...
}
); } } export default WBLaunchWorkflowPage;