Commit 1665fe34 authored by Jaden DIEFENBAUGH's avatar Jaden DIEFENBAUGH

[js][exp] add button to copy block settings to similar blocks, closes #73

parent f81dbbc3
......@@ -588,7 +588,7 @@ export class ExperimentEditor extends React.Component<Props, State> {
//Object.keys(this.props.data.contents.datasets).map(dsName
const newDs = JSON.parse(str);
console.log(newDs);
//console.log(newDs);
this.setContents({...this.props.data.contents, datasets: newDs});
for(const dataset in this.props.data.contents.datasets){
this.setLockMap(dataset, true);
......@@ -719,6 +719,21 @@ export class ExperimentEditor extends React.Component<Props, State> {
</FormGroup>
);
// update a block's data (and the globals object, if it changed)
updateBlock = (blockName: string, isAnalyzer: boolean, newBlock: any, globals: any = this.props.data.contents.globals) => {
if(isAnalyzer)
delete newBlock.outputs;
const bKey = isAnalyzer ? 'analyzers' : 'blocks';
const newBlocks = copyObj(this.props.data.contents[bKey]);
newBlocks[blockName] = newBlock;
this.setContents({
...this.props.data.contents,
[bKey]: newBlocks,
globals,
});
}
// renders the currently selected block to edit, if any
renderBlock = (blockName: string, block: any, isAnalyzer: boolean, key: number) => {
// gets the block info from the tc
......@@ -742,19 +757,8 @@ export class ExperimentEditor extends React.Component<Props, State> {
});
// func to update the exp's block info
const updateBlock = (newBlock, globals = this.props.data.contents.globals, newName = blockName) => {
if(isAnalyzer)
delete newBlock.outputs;
const bKey = isAnalyzer ? 'analyzers' : 'blocks';
const newBlocks = changeObjFieldName(this.props.data.contents[bKey], blockName, newName);
newBlocks[newName] = newBlock;
this.setContents({
...this.props.data.contents,
[bKey]: newBlocks,
globals,
});
};
const updateBlock = (newBlock: any, globals: any = this.props.data.contents.globals) =>
this.updateBlock(blockName, isAnalyzer, newBlock, globals);
// get info that could be from the block or from the global settings
const envInfo = block.environment || this.props.data.contents.globals.environment;
......@@ -834,69 +838,112 @@ export class ExperimentEditor extends React.Component<Props, State> {
<Label>
Algorithm
</Label>
<Input
type='select'
className='custom-select'
valid={block.algorithm !== ''}
value={block.algorithm}
onChange={e => {
// user selected a different algorithm
const str = e.target.value;
// find the alg or use a dummy algorithm instead
const alg = algorithms.find(nb => nb.name === str) || {name: '', contents: {groups: []}};
const [ inputs, outputs ] = algIOs(alg);
// try to map the IOs from the algorithm to the block via names
const findCloseIOMatch = (s: string, arr: string[]): string => {
if(arr.length === 1)
return arr[0];
const findRes = arr.find(str => str.includes(s));
if(findRes !== undefined)
return findRes;
return '';
};
// create the new block obj with the new alg
const thisBlock = {
...block,
algorithm: str,
};
// assign new inputs
if(tcBlock.inputs)
thisBlock.inputs = levMapStrings(Object.keys(inputs), tcBlock.inputs);
// assign new outputs
if(tcBlock.outputs)
thisBlock.outputs = levMapStrings(Object.keys(outputs), tcBlock.outputs);
// setup the parameters (erase & create stuff)
const globals = {
...this.props.data.contents.globals,
};
if(alg.contents.parameters && Object.keys(alg.contents.parameters).length > 0)
globals[alg.name] = {
...Object.entries(alg.contents.parameters || {})
.map(([pName, param]) => ({[pName]: getDefaultParameterValue(param)}))
.reduce((o, p) => ({...o, ...p}), {}),
...this.props.data.contents.globals[alg.name],
};
<FormGroup row>
<Col>
<Input
type='select'
className='custom-select'
valid={block.algorithm !== ''}
value={block.algorithm}
onChange={e => {
// user selected a different algorithm
const str = e.target.value;
// find the alg or use a dummy algorithm instead
const alg = algorithms.find(nb => nb.name === str) || {name: '', contents: {groups: []}};
const [ inputs, outputs ] = algIOs(alg);
// try to map the IOs from the algorithm to the block via names
const findCloseIOMatch = (s: string, arr: string[]): string => {
if(arr.length === 1)
return arr[0];
const findRes = arr.find(str => str.includes(s));
if(findRes !== undefined)
return findRes;
return '';
};
// create the new block obj with the new alg
const thisBlock = {
...block,
algorithm: str,
};
// assign new inputs
if(tcBlock.inputs)
thisBlock.inputs = levMapStrings(Object.keys(inputs), tcBlock.inputs);
// assign new outputs
if(tcBlock.outputs)
thisBlock.outputs = levMapStrings(Object.keys(outputs), tcBlock.outputs);
// setup the parameters (erase & create stuff)
const globals = copyObj(this.props.data.contents.globals);
// if the new alg has parameters, gen the global defaults for the params
if(alg.contents.parameters && Object.keys(alg.contents.parameters).length > 0)
globals[alg.name] = {
...Object.entries(alg.contents.parameters || {})
.map(([pName, param]) => ({[pName]: getDefaultParameterValue(param)}))
.reduce((o, p) => ({...o, ...p}), {}),
...this.props.data.contents.globals[alg.name],
};
if(globals.hasOwnProperty(block.algorithm) &&
[...this.props.toolchain.contents.analyzers, ...this.props.toolchain.contents.blocks]
.filter(b => b.algorithm === block.algorithm).length <= 1)
delete globals[block.algorithm];
// if the old algorithm had parameters and no other block is using that alg,
// delete the global param defaults for the old alg
if(globals.hasOwnProperty(block.algorithm) &&
[...this.props.toolchain.contents.analyzers, ...this.props.toolchain.contents.blocks]
.filter(b => b.algorithm === block.algorithm).length <= 1)
delete globals[block.algorithm];
updateBlock(thisBlock, globals);
this.setLockMap(blockName, true);
}}
>
<option value=''>Algorithm...</option>
{
possibleAlgorithms
.map(nb => nb.name)
.map((str, i) =>
<option key={i} value={str}>{ str }</option>
)
updateBlock(thisBlock, globals);
this.setLockMap(blockName, true);
}}
>
<option value=''>Algorithm...</option>
{
possibleAlgorithms
.map(nb => nb.name)
.map((str, i) =>
<option key={i} value={str}>{ str }</option>
)
}
</Input>
</Col>
{ !isAnalyzer &&
<Col sm='auto'>
<Button
color='primary'
disabled={block.algorithm === ''}
title={`Copies the algorithm & IO mappings to all unconfigured blocks with the same inputs & outputs`}
onClick={() => {
// copy the algorithm and input/output mappings to blocks
// that are the same except for the block name and dont have an algorithm assigned already
const targetBlocks = this.props.toolchain.contents.blocks
// not the block being copied from
.filter(b => b.name !== blockName)
// same IO
.filter(b => JSON.stringify(b.inputs) === JSON.stringify(tcBlock.inputs))
.filter(b => JSON.stringify(b.outputs) === JSON.stringify(tcBlock.outputs))
.map(b => b.name)
;
//console.log(targetBlocks);
// because the target blocks have the exact same inputs/outputs as the current block
// we dont need to re-compute the block object that we will copy!
// instead, just reuse the current block object and assign it to all
// the target blocks.
// also, because all these blocks are the same algorithm as the current block
// we dont need to mess with the globals.
const newBlocks = copyObj(this.props.data.contents.blocks);
targetBlocks.forEach(bName => { newBlocks[bName] = copyObj(block); });
this.setContents({
...this.props.data.contents,
blocks: newBlocks,
});
}}
>
Copy to similar blocks
</Button>
</Col>
}
</Input>
</FormGroup>
</FormGroup>
{ Object.entries(alg.contents.parameters || {}).length > 0 && <h5>Parameters</h5> }
{
......
......@@ -80,7 +80,7 @@ describe('<ExperimentEditor />', () => {
const tc = testTcs.find(tc => expName.includes(tc.name));
wrapper = mount(
<C
data={getValidObj({name: expName, contents: {}}, tc, [normalBlocks, ...analyzerBlocks])}
data={getValidObj({name: expName, contents: {}}, tc, [...normalBlocks, ...analyzerBlocks])}
experiments={[]}
normalBlocks={normalBlocks}
analyzerBlocks={analyzerBlocks}
......@@ -207,5 +207,306 @@ describe('<ExperimentEditor />', () => {
}
});
});
// tests:
// - assigning protocol for datasets
// - copy block algs
it('test/test/iris_advanced/1/iris', () => {
const expName ='test/test/iris_advanced/1/iris';
const saveFunc = sinon.spy();
const _updateFunc = (obj) => {
wrapper.setProps && wrapper.setProps({ data: obj });
};
const updateFunc = sinon.spy(_updateFunc);
const tc = testTcs.find(tc => expName.includes(tc.name));
wrapper = mount(
<C
data={getValidObj({name: expName, contents: {}}, tc, [...normalBlocks, ...analyzerBlocks])}
experiments={[]}
normalBlocks={normalBlocks}
analyzerBlocks={analyzerBlocks}
datasets={datasets}
toolchain={tc}
saveFunc={saveFunc}
environments={envs}
updateFunc={updateFunc}
/>
);
expect(wrapper.props().data).to.have.property('name', expName);
console.log('doing dataset');
wrapper.find('div.datasets select').at(0).simulate('change', { target: { value: '{"testing_data":{"database":"iris/1","protocol":"Main","set":"training"},"training_data":{"database":"iris/1","protocol":"Main","set":"testing"}}'}});
expect(updateFunc.callCount).to.equal(1);
expect(wrapper.props().data.contents).to.have.deep.property('datasets', {
'testing_data': {
'database': 'iris/1',
'protocol': 'Main',
'set': 'training'
},
'training_data': {
'database': 'iris/1',
'protocol': 'Main',
'set': 'testing'
}
});
//console.log('finished dataset, doing preprocessor blocks');
wrapper.find('svg #block_pre_training').simulate('click');
expect(wrapper.find({ name: 'pre_training', set: 'blocks'}).find('.tcBlockBackground').prop('className')).to.include('highlighted');
wrapper.find('div.block0 div.algorithm select').at(0).simulate('change', { target: { value: 'test/iris_preprocessor/1'}});
//console.log('finished preprocessor_training, copying alg to other preprocessors');
expect(updateFunc.callCount).to.equal(2);
wrapper.find('div.block0 div.algorithm button').at(0).simulate('click');
expect(updateFunc.callCount).to.equal(3);
expect(wrapper.props().data.contents).to.have.deep.property('blocks', {
'pre_testing': {
'algorithm': 'test/iris_preprocessor/1',
'inputs': {
'measurements': 'measurements'
},
'outputs': {
'measurements': 'measurements'
},
'parameters': {}
},
'pre_training': {
'algorithm': 'test/iris_preprocessor/1',
'inputs': {
'measurements': 'measurements'
},
'outputs': {
'measurements': 'measurements'
},
'parameters': {}
},
'testing_alg': {
'algorithm': '',
'inputs': {
'lda_machine': '',
'measurements': ''
},
'outputs': {
'scores': ''
},
'parameters': {}
},
'training_alg': {
'algorithm': '',
'inputs': {
'measurements': '',
'species': ''
},
'outputs': {
'lda_machine': ''
},
'parameters': {}
}
});
//console.log('finished preprocessors, doing training');
wrapper.find('svg #block_training_alg').simulate('click');
expect(wrapper.find({ name: 'training_alg', set: 'blocks'}).find('.tcBlockBackground').prop('className')).to.include('highlighted');
wrapper.find('div.block0 div.algorithm select').at(0).simulate('change', { target: { value: 'test/iris_training/1'}});
expect(updateFunc.callCount).to.equal(4);
expect(wrapper.props().data.contents).to.have.deep.property('blocks', {
'pre_testing': {
'algorithm': 'test/iris_preprocessor/1',
'inputs': {
'measurements': 'measurements'
},
'outputs': {
'measurements': 'measurements'
},
'parameters': {}
},
'pre_training': {
'algorithm': 'test/iris_preprocessor/1',
'inputs': {
'measurements': 'measurements'
},
'outputs': {
'measurements': 'measurements'
},
'parameters': {}
},
'testing_alg': {
'algorithm': '',
'inputs': {
'lda_machine': '',
'measurements': ''
},
'outputs': {
'scores': ''
},
'parameters': {}
},
'training_alg': {
'algorithm': 'test/iris_training/1',
'inputs': {
'measurements': 'measurements',
'species': 'species'
},
'outputs': {
'lda_machine': 'lda_machine'
},
'parameters': {}
}
});
//console.log('finished training, doing testing');
wrapper.find('svg #block_testing_alg').simulate('click');
expect(wrapper.find({ name: 'testing_alg', set: 'blocks'}).find('.tcBlockBackground').prop('className')).to.include('highlighted');
wrapper.find('div.block0 div.algorithm select').at(0).simulate('change', { target: { value: 'test/iris_testing/1'}});
expect(updateFunc.callCount).to.equal(5);
expect(wrapper.props().data.contents).to.have.deep.property('blocks', {
'pre_testing': {
'algorithm': 'test/iris_preprocessor/1',
'inputs': {
'measurements': 'measurements'
},
'outputs': {
'measurements': 'measurements'
},
'parameters': {}
},
'pre_training': {
'algorithm': 'test/iris_preprocessor/1',
'inputs': {
'measurements': 'measurements'
},
'outputs': {
'measurements': 'measurements'
},
'parameters': {}
},
'testing_alg': {
'algorithm': 'test/iris_testing/1',
'inputs': {
'lda_machine': 'lda_machine',
'measurements': 'measurements'
},
'outputs': {
'scores': 'scores'
},
'parameters': {}
},
'training_alg': {
'algorithm': 'test/iris_training/1',
'inputs': {
'measurements': 'measurements',
'species': 'species'
},
'outputs': {
'lda_machine': 'lda_machine'
},
'parameters': {}
}
});
//console.log('finished blocks, doing analyzer');
wrapper.find('svg #block_analyzer').simulate('click');
expect(wrapper.find({ name: 'analyzer', set: 'analyzers'}).find('.tcBlockBackground').prop('className')).to.include('highlighted');
wrapper.find('div.block0 div.algorithm select').at(0).simulate('change', { target: { value: 'test/iris_analyzer/1'}});
expect(updateFunc.callCount).to.equal(6);
expect(wrapper.props().data.contents).to.have.deep.property('analyzers', {
'analyzer': {
'algorithm': 'test/iris_analyzer/1',
'inputs': {
'scores': 'scores',
'species': 'species'
},
'parameters': {}
}
});
//console.log('finished analyzer, doing cache check');
expect(wrapper.props().data).to.be.deep.equal({
'name': 'test/test/iris_advanced/1/iris',
'contents': {
'description': '',
'analyzers': {
'analyzer': {
'algorithm': 'test/iris_analyzer/1',
'inputs': {
'scores': 'scores',
'species': 'species'
},
'parameters': {}
}
},
'blocks': {
'pre_testing': {
'algorithm': 'test/iris_preprocessor/1',
'inputs': {
'measurements': 'measurements'
},
'outputs': {
'measurements': 'measurements'
},
'parameters': {}
},
'pre_training': {
'algorithm': 'test/iris_preprocessor/1',
'inputs': {
'measurements': 'measurements'
},
'outputs': {
'measurements': 'measurements'
},
'parameters': {}
},
'testing_alg': {
'algorithm': 'test/iris_testing/1',
'inputs': {
'lda_machine': 'lda_machine',
'measurements': 'measurements'
},
'outputs': {
'scores': 'scores'
},
'parameters': {}
},
'training_alg': {
'algorithm': 'test/iris_training/1',
'inputs': {
'measurements': 'measurements',
'species': 'species'
},
'outputs': {
'lda_machine': 'lda_machine'
},
'parameters': {}
}
},
'datasets': {
'testing_data': {
'database': 'iris/1',
'protocol': 'Main',
'set': 'training'
},
'training_data': {
'database': 'iris/1',
'protocol': 'Main',
'set': 'testing'
}
},
'globals': {
'queue': 'Default',
'environment': {
'name': 'Scientific Python 2.7',
'version': '0.0.4'
}
}
}
});
});
});
});
This diff is collapsed.
[
{
"name": "iris/1",
"contents": {
"description": "",
"root_folder": "/not/needed",
"protocols": [
{
"name": "Main",
"sets": [
{
"parameters": {},
"name": "training",
"outputs": {
"measurements": "system/array_1d_floats/1",
"species": "system/text/1"
},
"template": "iris",
"view": "Training"
},
{
"parameters": {},
"name": "testing",
"outputs": {
"measurements": "system/array_1d_floats/1",
"species": "system/text/1"
},
"template": "iris",
"view": "Testing"
}
],
"template": "iris"
}
]
}
},
{
"name": "atnt/3",
"contents": {
......
[
{
"name": "test/test/iris_advanced/1/iris",
"contents": {
"description": "",
"analyzers": {
"analyzer": {
"algorithm": "test/iris_analyzer/1",
"inputs": {
"scores": "scores",
"species": "species"
},
"parameters": {}
}
},
"blocks": {
"pre_testing": {
"algorithm": "test/iris_preprocessor/1",
"inputs": {
"measurements": "measurements"
},
"outputs": {
"measurements": "measurements"
},
"parameters": {}
},
"pre_training": {
"algorithm": "test/iris_preprocessor/1",
"inputs": {
"measurements": "measurements"
},
"outputs": {
"measurements": "measurements"
},
"parameters": {}
},
"testing_alg": {
"algorithm": "test/iris_testing/1",
"inputs": {
"lda_machine": "lda_machine",
"measurements": "measurements"
},
"outputs": {
"scores": "scores"
},
"parameters": {}
},
"training_alg": {
"algorithm": "test/iris_training/1",
"inputs": {
"measurements": "measurements",
"species": "species"
},
"outputs": {
"lda_machine": "lda_machine"
},
"parameters": {}
}
},
"datasets": {
"testing_data": {
"database": "iris/1",
"protocol": "Main",
"set": "training"
},
"training_data": {
"database": "iris/1",
"protocol": "Main",
"set": "testing"
}
},
"globals": {
"queue": "Default",
"environment": {
"name": "Scientific Python 2.7",
"version": "0.0.4"
}
}
}
},
{
"name": "tutorial/tutorial/eigenface/1/atnt-eigenfaces-7-comps",
"contents": {
......