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

Working on workflow launcher.

pull/1/head
parent
commit
880eee342b
2 changed files with 85 additions and 140 deletions
  1. +20
    -12
      frontend/src/js/dialog/wb-browse-dialog.js
  2. +65
    -128
      frontend/src/js/page/wb-launch-workflow-page.js

+ 20
- 12
frontend/src/js/dialog/wb-browse-dialog.js View File

@@ -20,7 +20,7 @@ import { createHashHistory } from 'history';
//
// props:
// selectMany: Boolean
// selectWhat: [ 'file', 'directory' ]
// selectWhat: [ 'file', 'directory', 'owner' ]
//
// state:
// selected: Array of UUID
@@ -43,6 +43,7 @@ class WBBrowseDialog extends Component {
this.state.collectionPath = '';
this.state.textSearch = '';
this.state.id = ('id' in this.props) ? this.props.id : uuid.v4();
this.state.accept = () => {};
}
navigateBack() {
@@ -52,7 +53,7 @@ class WBBrowseDialog extends Component {
this.navigate(url, false);
}
navigate(url, useHistory=true) {
navigate(url, useHistory=true, stateUpdate={}) {
if (typeof(url) === 'object') {
url = ['', 'browse-dialog',
'mode' in url ? url.mode : this.state.mode,
@@ -71,11 +72,11 @@ class WBBrowseDialog extends Component {
let [ _1, _2, mode, uuid, topPage, bottomPage, textSearch, collectionPath ] = url.split('/');
topPage = parseInt(topPage, 10) || 0;
bottomPage = parseInt(bottomPage, 10) || 0;
collectionPath = decodeURIComponent(collectionPath);
this.setState({
collectionPath = decodeURIComponent(collectionPath || '');
this.setState(Object.assign({
'currentUrl': url,
mode, uuid, topPage, bottomPage, textSearch, collectionPath
});
}, stateUpdate));
}
select(uuid) {
@@ -111,14 +112,13 @@ class WBBrowseDialog extends Component {
}
makeSelectionCell(uuid) {
const { selectMany, id, accept } = this.props;
const { selected } = this.state;
const { selected, accept, selectMany, id } = this.state;
return selectMany ? (
<div>
<input type="checkbox" checked={ (uuid in selected) }
onChange={ e => {
if (e.target.value === 'on')
if (e.target.checked)
this.select(uuid);
else
this.deselect(uuid);
@@ -135,14 +135,19 @@ class WBBrowseDialog extends Component {
);
}
show(callback) {
show(selectWhat, selectMany, accept=(() => {})) {
const { app } = this.props;
const { currentUser } = app.state;
this.navigate('/browse-dialog/browse/' + currentUser.uuid, false,
{ selectWhat, selectMany, accept, history: [] });
$('#' + this.state.id).modal();
}
render({ app, selectMany, selectWhat },
render({ app },
{ history, currentUrl, mode, uuid,
topPage, bottomPage, textSearch,
collectionPath, id }) {
collectionPath, id, accept, selectedOrder,
selectMany, selectWhat }) {
return (
<div class="modal" id={ id } tabindex="-1" role="dialog">
@@ -231,7 +236,10 @@ class WBBrowseDialog extends Component {
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary">Accept</button>
{ selectMany ? (
<button type="button" class="btn btn-primary"
onclick={ e => { e.preventDefault(); accept(selectedOrder); } }>Accept</button>
) : null }
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
</div>
</div>


+ 65
- 128
frontend/src/js/page/wb-launch-workflow-page.js View File

@@ -1,66 +1,26 @@
import { h, Component, createRef } from 'preact';
import WBNavbarCommon from 'wb-navbar-common';
import WBArvadosCrumbs from 'wb-arvados-crumbs';
import WBToolboxDialog from 'wb-toolbox-dialog';
import WBBrowseDialog from 'wb-browse-dialog';
import WBTable from 'wb-table';
import makeArvadosRequest from 'make-arvados-request';
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 {
constructor(...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() {
@@ -71,47 +31,64 @@ class WBLaunchWorkflowPage extends Component {
'/arvados/v1/workflows/' + workflowUuid);
prom = prom.then(xhr => this.setState({
'workflow': xhr.response,
'workflowDefinition': parseDefinition(xhr.response.definition),
'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 },
{ workflow, projectUuid, processName, processDescription,
defaultProcessName, defaultProcessDescription,
inputsFunctionText, browseDialogId,
insertDialogId, insertManyDialogId,
realBrowseDialogId, inputsPreview }) {
{ workflow, workflowDefinition, projectUuid, processName, processDescription,
defaultProcessName, defaultProcessDescription }) {
return (
<div>
<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 ?
(<form class="container-fluid">
@@ -138,17 +115,13 @@ class WBLaunchWorkflowPage extends Component {
<div class="input-group-append">
<button class="btn btn-primary" type="button"
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 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">
<label for="processName">Process Name</label>
<input type="text" class="form-control" id="processName"
@@ -165,47 +138,11 @@ class WBLaunchWorkflowPage extends Component {
<div class="form-group">
<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>
</form>) : <div>Loading...</div> }
</div>


Loading…
Cancel
Save