|
@@ -1,66 +1,26 @@ |
|
|
import { h, Component, createRef } from 'preact';
|
|
|
import { h, Component, createRef } from 'preact';
|
|
|
import WBNavbarCommon from 'wb-navbar-common';
|
|
|
import WBNavbarCommon from 'wb-navbar-common';
|
|
|
import WBArvadosCrumbs from 'wb-arvados-crumbs';
|
|
|
import WBArvadosCrumbs from 'wb-arvados-crumbs';
|
|
|
import WBToolboxDialog from 'wb-toolbox-dialog';
|
|
|
|
|
|
import WBBrowseDialog from 'wb-browse-dialog';
|
|
|
import WBBrowseDialog from 'wb-browse-dialog';
|
|
|
|
|
|
import WBTable from 'wb-table';
|
|
|
import makeArvadosRequest from 'make-arvados-request';
|
|
|
import makeArvadosRequest from 'make-arvados-request';
|
|
|
import linkState from 'linkstate';
|
|
|
import linkState from 'linkstate';
|
|
|
|
|
|
|
|
|
function createInputsTemplate(workflow) {
|
|
|
|
|
|
const g = JSON.parse(workflow.definition)['$graph'];
|
|
|
|
|
|
const main = g.find(it => (it.id === '#main'));
|
|
|
|
|
|
let res = '';
|
|
|
|
|
|
main.inputs.map(it => {
|
|
|
|
|
|
let id = it.id.split('/');
|
|
|
|
|
|
id = id[id.length - 1];
|
|
|
|
|
|
if (it.label) res += ' // ' + it.label + '\n';
|
|
|
|
|
|
if (it.doc) res += ' // ' + it.doc + '\n';
|
|
|
|
|
|
res += ' // Type: ' + ((typeof(it.type) === 'string') ?
|
|
|
|
|
|
it.type : JSON.stringify(it.type)) + '\n\n';
|
|
|
|
|
|
res += ' \'' + id + '\': \'\',\n\n';
|
|
|
|
|
|
//res += ' // ' + ' '.repeat(id.length) + '^^^^\n\n';
|
|
|
|
|
|
});
|
|
|
|
|
|
/* let res = main.inputs.map(it => { it.value = null; return it; });
|
|
|
|
|
|
res = JSON.stringify(res, null, 2);
|
|
|
|
|
|
res = res.split('\n');
|
|
|
|
|
|
res = res.map((ln, i) => (i == 0 ? ln : ' ' + ln));
|
|
|
|
|
|
res = res.join('\n'); */
|
|
|
|
|
|
return res;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function uuidsToCwlObjects(spec) {
|
|
|
|
|
|
if (typeof(spec) === 'string') {
|
|
|
|
|
|
if (/^[a-z0-9]{5}-[a-z0-9]{5}-[a-z0-9]{15}/.exec(spec)) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
'class': 'Directory',
|
|
|
|
|
|
'location': 'keep:' + spec
|
|
|
|
|
|
};
|
|
|
|
|
|
} else if (/^[a-f0-9]{32}\+[0-9]+/.exec(spec)) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
'class': 'Directory',
|
|
|
|
|
|
'location': 'keep:' + spec
|
|
|
|
|
|
};
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return spec;
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (typeof(spec) === 'object') {
|
|
|
|
|
|
const res = (spec instanceof Array) ? [] : {};
|
|
|
|
|
|
Object.keys(spec).map(k => (res[k] = uuidsToCwlObjects(spec[k])));
|
|
|
|
|
|
return res;
|
|
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return spec;
|
|
|
|
|
|
|
|
|
function parseDefinition(text) {
|
|
|
|
|
|
let definition;
|
|
|
|
|
|
try {
|
|
|
|
|
|
definition = JSON.parse(text);
|
|
|
|
|
|
} catch (_) {
|
|
|
|
|
|
definition = jsyaml.load(text);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return definition;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
class WBLaunchWorkflowPage extends Component {
|
|
|
class WBLaunchWorkflowPage extends Component {
|
|
|
constructor(...args) {
|
|
|
constructor(...args) {
|
|
|
super(...args);
|
|
|
super(...args);
|
|
|
this.state.browseDialogId = uuid.v4();
|
|
|
|
|
|
this.state.insertDialogId = uuid.v4();
|
|
|
|
|
|
this.state.insertManyDialogId = uuid.v4();
|
|
|
|
|
|
this.state.realBrowseDialogId = uuid.v4();
|
|
|
|
|
|
this.inputsTextArea = createRef();
|
|
|
|
|
|
|
|
|
this.browseDialogRef = createRef();
|
|
|
|
|
|
this.state.inputs = {};
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
componentDidMount() {
|
|
|
componentDidMount() {
|
|
@@ -71,47 +31,64 @@ class WBLaunchWorkflowPage extends Component { |
|
|
'/arvados/v1/workflows/' + workflowUuid);
|
|
|
'/arvados/v1/workflows/' + workflowUuid);
|
|
|
prom = prom.then(xhr => this.setState({
|
|
|
prom = prom.then(xhr => this.setState({
|
|
|
'workflow': xhr.response,
|
|
|
'workflow': xhr.response,
|
|
|
|
|
|
'workflowDefinition': parseDefinition(xhr.response.definition),
|
|
|
'defaultProcessName': xhr.response.name + ' ' + (new Date().toISOString()),
|
|
|
'defaultProcessName': xhr.response.name + ' ' + (new Date().toISOString()),
|
|
|
'defaultProcessDescription': xhr.response.description,
|
|
|
|
|
|
'inputsFunctionText': '(() => {\n return {\n' +
|
|
|
|
|
|
createInputsTemplate(xhr.response) +
|
|
|
|
|
|
' };\n})()'
|
|
|
|
|
|
|
|
|
'defaultProcessDescription': xhr.response.description
|
|
|
}));
|
|
|
}));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
renderInput(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');
|
|
|
|
|
|
|
|
|
|
|
|
if (!isFile && !isDirectory)
|
|
|
|
|
|
return (
|
|
|
|
|
|
<input class="form-control w-100" type="text" placeholder={ inputSpec.doc }
|
|
|
|
|
|
value={ this.state.inputs[inputSpec.id] }
|
|
|
|
|
|
onchange={ e => (this.state.inputs[inputSpec.id] = e.target.value) }></input>
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const button = (
|
|
|
|
|
|
<button class="btn btn-outline-primary"
|
|
|
|
|
|
onclick={ e => {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
this.browseDialogRef.current.show(isFile ? 'file' : 'directory', isArray,
|
|
|
|
|
|
v => {
|
|
|
|
|
|
this.state.inputs[inputSpec.id] = v.toString();
|
|
|
|
|
|
this.setState({});
|
|
|
|
|
|
});
|
|
|
|
|
|
} }>
|
|
|
|
|
|
Browse...
|
|
|
|
|
|
</button>
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div class="input-group">
|
|
|
|
|
|
<input class="form-control w-100" type="text" placeholder={ inputSpec.doc }
|
|
|
|
|
|
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>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
render({ app, workflowUuid },
|
|
|
render({ app, workflowUuid },
|
|
|
{ workflow, projectUuid, processName, processDescription,
|
|
|
|
|
|
defaultProcessName, defaultProcessDescription,
|
|
|
|
|
|
inputsFunctionText, browseDialogId,
|
|
|
|
|
|
insertDialogId, insertManyDialogId,
|
|
|
|
|
|
realBrowseDialogId, inputsPreview }) {
|
|
|
|
|
|
|
|
|
{ workflow, workflowDefinition, projectUuid, processName, processDescription,
|
|
|
|
|
|
defaultProcessName, defaultProcessDescription }) {
|
|
|
|
|
|
|
|
|
return (
|
|
|
return (
|
|
|
<div>
|
|
|
<div>
|
|
|
<WBNavbarCommon app={ app } />
|
|
|
<WBNavbarCommon app={ app } />
|
|
|
|
|
|
|
|
|
<WBToolboxDialog app={ app } id={ browseDialogId }
|
|
|
|
|
|
items={ app.state.toolboxItems } onAccepted={ value =>
|
|
|
|
|
|
this.setState({ 'projectUuid': value }) } />
|
|
|
|
|
|
|
|
|
|
|
|
<WBToolboxDialog app={ app } id={ insertDialogId }
|
|
|
|
|
|
items={ app.state.toolboxItems }
|
|
|
|
|
|
onAccepted={ value => {
|
|
|
|
|
|
const t = this.inputsTextArea.current;
|
|
|
|
|
|
const start = t.selectionStart;
|
|
|
|
|
|
const end = t.selectionEnd;
|
|
|
|
|
|
this.setState({
|
|
|
|
|
|
'inputsFunctionText': t.value.substr(0, start) + value +
|
|
|
|
|
|
t.value.substr(end)
|
|
|
|
|
|
});
|
|
|
|
|
|
} } />
|
|
|
|
|
|
|
|
|
|
|
|
<WBToolboxDialog app={ app } id={ insertManyDialogId }
|
|
|
|
|
|
items={ app.state.toolboxItems } selectMany={ true }
|
|
|
|
|
|
onAccepted={ values => alert(values) } />
|
|
|
|
|
|
|
|
|
|
|
|
<WBBrowseDialog app={ app } id={ realBrowseDialogId }
|
|
|
|
|
|
selectWhat="directory" />
|
|
|
|
|
|
|
|
|
<WBBrowseDialog app={ app } ref={ this.browseDialogRef } />
|
|
|
|
|
|
|
|
|
{ workflow ?
|
|
|
{ workflow ?
|
|
|
(<form class="container-fluid">
|
|
|
(<form class="container-fluid">
|
|
@@ -138,17 +115,13 @@ class WBLaunchWorkflowPage extends Component { |
|
|
<div class="input-group-append">
|
|
|
<div class="input-group-append">
|
|
|
<button class="btn btn-primary" type="button"
|
|
|
<button class="btn btn-primary" type="button"
|
|
|
id="button-addon2" onclick={ e => { e.preventDefault();
|
|
|
id="button-addon2" onclick={ e => { e.preventDefault();
|
|
|
$('#' + browseDialogId).modal(); } }>Browse</button>
|
|
|
|
|
|
|
|
|
this.browseDialogRef.current.show('owner', false,
|
|
|
|
|
|
projectUuid => this.setState({ projectUuid })); } }>Browse</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
<div class="form-check">
|
|
|
|
|
|
<input type="checkbox" class="form-check-input" id="createSubproject" />
|
|
|
|
|
|
<label class="form-check-label" for="createSubproject">Create subproject</label>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="form-group">
|
|
|
<div class="form-group">
|
|
|
<label for="processName">Process Name</label>
|
|
|
<label for="processName">Process Name</label>
|
|
|
<input type="text" class="form-control" id="processName"
|
|
|
<input type="text" class="form-control" id="processName"
|
|
@@ -165,47 +138,11 @@ class WBLaunchWorkflowPage extends Component { |
|
|
|
|
|
|
|
|
<div class="form-group">
|
|
|
<div class="form-group">
|
|
|
<label for="inputs">Inputs</label>
|
|
|
<label for="inputs">Inputs</label>
|
|
|
<div>
|
|
|
|
|
|
<div class="mb-2">
|
|
|
|
|
|
<button class="btn btn-primary mr-2" onclick={ e => {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
$('#' + insertDialogId).modal();
|
|
|
|
|
|
} }>Insert</button>
|
|
|
|
|
|
|
|
|
|
|
|
<button class="btn btn-primary mr-2" onclick={ e => {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
$('#' + insertManyDialogId).modal();
|
|
|
|
|
|
} }>Insert Many</button>
|
|
|
|
|
|
|
|
|
|
|
|
<button class="btn btn-primary mr-2" onclick={ e => {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
$('#' + realBrowseDialogId).modal();
|
|
|
|
|
|
} }>Insert Browse</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<textarea class="form-control" ref={ this.inputsTextArea } id="inputs"
|
|
|
|
|
|
style="font-family: monospace;" rows="20"
|
|
|
|
|
|
value={ inputsFunctionText }
|
|
|
|
|
|
onChange={ linkState(this, 'inputsFunctionText') }></textarea>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="my-2">
|
|
|
|
|
|
<button class="btn btn-primary" onclick={ e => {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
try {
|
|
|
|
|
|
let inputsVal = eval(inputsFunctionText);
|
|
|
|
|
|
inputsVal = uuidsToCwlObjects(inputsVal);
|
|
|
|
|
|
this.setState({ 'inputsPreview': JSON.stringify(inputsVal, null, 2) });
|
|
|
|
|
|
} catch (exc) {
|
|
|
|
|
|
this.setState({ 'inputsPreview': exc });
|
|
|
|
|
|
}
|
|
|
|
|
|
} }>Preview</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<textarea class="form-control" readonly="readonly"
|
|
|
|
|
|
style="font-family: monospace;" rows="10"
|
|
|
|
|
|
value={ inputsPreview }></textarea>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<WBTable columns={ [ 'Name', 'Value'] }
|
|
|
|
|
|
rows={ workflowDefinition.$graph.find(a => (a.id === '#main')).inputs.map(it => [
|
|
|
|
|
|
it.label || it.id,
|
|
|
|
|
|
this.renderInput(it)
|
|
|
|
|
|
]) } />
|
|
|
</div>
|
|
|
</div>
|
|
|
</form>) : <div>Loading...</div> }
|
|
|
</form>) : <div>Loading...</div> }
|
|
|
</div>
|
|
|
</div>
|
|
|