Commit bbd42e2a authored by Jaden DIEFENBAUGH's avatar Jaden DIEFENBAUGH

[js][exp] refactor exp editor and tests

parent 1dbb4ab7
......@@ -34,6 +34,7 @@ import type { BeatObject, ParameterValue, BeatEnvironment } from '@helpers/beat'
import { changeObjFieldName, sortObject, jsonClone } from '@helpers';
import * as Selectors from '@store/selectors.js';
import * as Actions from '@store/actions.js';
import ValidSchemaBadge from '../ValidSchemaBadge.jsx';
import CacheInput from '../CacheInput.jsx';
......@@ -55,10 +56,10 @@ type Props = {
toolchain: BeatObject,
environments: { [string]: BeatEnvironment },
saveFunc: (BeatObject) => any,
updateFunc: (BeatObject) => any,
};
type State = {
cache: any,
/* LockMap: map of which blocks are used in the type inference
* This is a per-block mapping of which blocks are used for type inference.
* There are more references (including some commented-out buttons & such) to
......@@ -324,7 +325,6 @@ export class ExperimentEditor extends React.Component<Props, State> {
}
state = {
cache: getValidObj(this.props.data, this.props.toolchain, [...this.props.normalBlocks, ...this.props.analyzerBlocks]),
lockMap: genLockMap(this.props.toolchain),
activeBlockInfo: {
name: undefined,
......@@ -333,22 +333,12 @@ export class ExperimentEditor extends React.Component<Props, State> {
disableTypeInference: false,
}
componentWillReceiveProps (nextProps: Props) {
this.setState({
...this.state,
cache: getValidObj(nextProps.data, nextProps.toolchain, [...this.props.normalBlocks, ...this.props.analyzerBlocks]),
lockMap: genLockMap(nextProps.toolchain),
});
}
setContents = (newContents: any) => {
this.setState({
cache: {
...this.state.cache,
contents: {
'description': this.state.cache.contents['description'],
...newContents,
}
this.props.updateFunc({
...this.props.data,
contents: {
'description': this.props.data.contents['description'],
...newContents,
}
});
}
......@@ -360,7 +350,6 @@ export class ExperimentEditor extends React.Component<Props, State> {
name: blockName
};
this.setState({
...this.state,
activeBlockInfo: newMBI,
});
......@@ -385,7 +374,7 @@ export class ExperimentEditor extends React.Component<Props, State> {
// gets parameter data for all the blocks with selected algorithms
getParameterObjs = (): any[] => {
const algs = [...this.props.normalBlocks, ...this.props.analyzerBlocks];
return Object.entries({...this.state.cache.contents.blocks, ...this.state.cache.contents.analyzers})
return Object.entries({...this.props.data.contents.blocks, ...this.props.data.contents.analyzers})
.map(([bName, block]) => algs.find(a => a.name === block.algorithm))
.filter(a => a !== null && a !== undefined && a.contents.parameters !== undefined && Object.keys(a.contents.parameters).length > 0)
.map(a => [a.name, a.contents.parameters])
......@@ -399,7 +388,7 @@ export class ExperimentEditor extends React.Component<Props, State> {
getConnectionInferredTypes = (): any => {
const isLocked = (name) => !this.state.disableTypeInference && this.state.lockMap[name];
const dsTypes = Object.entries(this.state.cache.contents.datasets)
const dsTypes = Object.entries(this.props.data.contents.datasets)
.reduce((o, [name, ds]) => {
const d = this.props.datasets.find(pds => datasetFieldToString(pds) === datasetFieldToString(ds));
const outputs = d ? d.outputs : {};
......@@ -409,14 +398,14 @@ export class ExperimentEditor extends React.Component<Props, State> {
};
}, {});
const nTypes = Object.entries(this.state.cache.contents.blocks)
const nTypes = Object.entries(this.props.data.contents.blocks)
.filter(([name, block]) => isLocked(name))
.reduce((o, [name, block]) => ({
...o,
[name]: getBlockIOTypes(block, this.props.normalBlocks),
}), {});
const aTypes = Object.entries(this.state.cache.contents.analyzers)
const aTypes = Object.entries(this.props.data.contents.analyzers)
.filter(([name, block]) => isLocked(name))
.reduce((o, [name, block]) => ({
...o,
......@@ -431,8 +420,8 @@ export class ExperimentEditor extends React.Component<Props, State> {
}), {});
const missingBlocks = Object.entries({
...this.state.cache.contents.blocks,
...this.state.cache.contents.analyzers
...this.props.data.contents.blocks,
...this.props.data.contents.analyzers
})
.filter(([name, block]) => !nTypes.hasOwnProperty(name) && !aTypes.hasOwnProperty(name))
.reduce((o, [name, block]) => ({...o, [name]: getBlockIOTypes(block, [...this.props.normalBlocks, ...this.props.analyzerBlocks])}), {});
......@@ -497,21 +486,21 @@ export class ExperimentEditor extends React.Component<Props, State> {
algorithmMissing: string[],
ioMissing: string[],
} => {
const dataSourceMissing = Object.entries(this.state.cache.contents.datasets)
const dataSourceMissing = Object.entries(this.props.data.contents.datasets)
.filter(([name, ds]) => ds.set === '')
.map(([name, b]) => name)
;
const algorithmMissing = [
...Object.entries(this.state.cache.contents.blocks),
...Object.entries(this.state.cache.contents.analyzers),
...Object.entries(this.props.data.contents.blocks),
...Object.entries(this.props.data.contents.analyzers),
].filter(([name, b]) => b.algorithm === '')
.map(([name, b]) => name)
;
const ioMissing = [
...Object.entries(this.state.cache.contents.blocks),
...Object.entries(this.state.cache.contents.analyzers),
...Object.entries(this.props.data.contents.blocks),
...Object.entries(this.props.data.contents.analyzers),
].filter(([name, b]) => {
if(b.algorithm === '')
return false;
......@@ -528,7 +517,7 @@ export class ExperimentEditor extends React.Component<Props, State> {
.map(([name, b]) => name)
;
const dsTypes = Object.entries(this.state.cache.contents.datasets)
const dsTypes = Object.entries(this.props.data.contents.datasets)
.reduce((o, [name, ds]) => {
const d = this.props.datasets.find(pds => datasetFieldToString(pds) === datasetFieldToString(ds));
const outputs = d ? d.outputs : {};
......@@ -538,13 +527,13 @@ export class ExperimentEditor extends React.Component<Props, State> {
};
}, {});
const nTypes = Object.entries(this.state.cache.contents.blocks)
const nTypes = Object.entries(this.props.data.contents.blocks)
.reduce((o, [name, block]) => ({
...o,
[name]: getBlockIOTypes(block, this.props.normalBlocks),
}), {});
const aTypes = Object.entries(this.state.cache.contents.analyzers)
const aTypes = Object.entries(this.props.data.contents.analyzers)
.reduce((o, [name, block]) => ({
...o,
[name]: getBlockIOTypes(block, this.props.analyzerBlocks),
......@@ -593,15 +582,15 @@ export class ExperimentEditor extends React.Component<Props, State> {
<Input
type='select'
className='custom-select'
value={JSON.stringify(sortObject(this.state.cache.contents.datasets))}
value={JSON.stringify(sortObject(this.props.data.contents.datasets))}
onChange={e => {
const str = e.target.value;
//Object.keys(this.state.cache.contents.datasets).map(dsName
//Object.keys(this.props.data.contents.datasets).map(dsName
const newDs = JSON.parse(str);
console.log(newDs);
this.setContents({...this.state.cache.contents, datasets: newDs});
for(const dataset in this.state.cache.contents.datasets){
this.setContents({...this.props.data.contents, datasets: newDs});
for(const dataset in this.props.data.contents.datasets){
this.setLockMap(dataset, true);
}
}}
......@@ -622,7 +611,7 @@ export class ExperimentEditor extends React.Component<Props, State> {
}, {})
)
.map(([dbProtStr, sets]) => {
const enoughSets = sets.length == Object.keys(this.state.cache.contents.datasets).length;
const enoughSets = sets.length == Object.keys(this.props.data.contents.datasets).length;
if(!enoughSets)
return [dbProtStr, false];
......@@ -656,7 +645,7 @@ export class ExperimentEditor extends React.Component<Props, State> {
</Input>
</FormGroup>
{
(Object.entries(this.state.cache.contents.datasets): [string, any][])
(Object.entries(this.props.data.contents.datasets): [string, any][])
.sort(([n1, ds1], [n2, ds2]) => n1 > n2 ? 1 : -1)
.map(([name, dataset], i, dEntries) => {
const inferredTypes = this.getConnectionInferredTypes();
......@@ -701,8 +690,8 @@ export class ExperimentEditor extends React.Component<Props, State> {
database: res[3] || '',
};
const newDs = {...this.state.cache.contents.datasets, [name]: ds};
this.setContents({...this.state.cache.contents, datasets: newDs});
const newDs = {...this.props.data.contents.datasets, [name]: ds};
this.setContents({...this.props.data.contents, datasets: newDs});
this.setLockMap(name, true);
}}
>
......@@ -750,22 +739,22 @@ export class ExperimentEditor extends React.Component<Props, State> {
return isValidEntity(blockName, tcBlock, {inputs: iAlg, outputs: oAlg}, inferredTypes);
});
const updateBlock = (newBlock, globals = this.state.cache.contents.globals, newName = blockName) => {
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.state.cache.contents[bKey], blockName, newName);
const newBlocks = changeObjFieldName(this.props.data.contents[bKey], blockName, newName);
newBlocks[newName] = newBlock;
this.setContents({
...this.state.cache.contents,
...this.props.data.contents,
[bKey]: newBlocks,
globals,
});
};
const envInfo = block.environment || this.state.cache.contents.globals.environment;
const queue = block.queue || this.state.cache.contents.globals.queue;
const envInfo = block.environment || this.props.data.contents.globals.environment;
const queue = block.queue || this.props.data.contents.globals.queue;
const envDisabled = !block.hasOwnProperty('environment');
return (
......@@ -881,14 +870,14 @@ export class ExperimentEditor extends React.Component<Props, State> {
thisBlock.outputs = levMapStrings(Object.keys(outputs), tcBlock.outputs);
const globals = {
...this.state.cache.contents.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.state.cache.contents.globals[alg.name],
...this.props.data.contents.globals[alg.name],
};
if(globals.hasOwnProperty(block.algorithm) &&
......@@ -914,7 +903,7 @@ export class ExperimentEditor extends React.Component<Props, State> {
{
Object.entries(alg.contents.parameters || {}).map(([pName, paramObj], i) => {
const isTethered = !block.parameters.hasOwnProperty(pName);
const globalParams = this.state.cache.contents.globals[alg.name];
const globalParams = this.props.data.contents.globals[alg.name];
const currVal = block.parameters[pName] || globalParams[pName];
const deleteParam = () => {
......@@ -1055,7 +1044,7 @@ export class ExperimentEditor extends React.Component<Props, State> {
renderBlocks = () => this.state.activeBlockInfo.set === 'blocks' && (
<FormGroup>
{
(Object.entries(this.state.cache.contents.blocks): [string, any][])
(Object.entries(this.props.data.contents.blocks): [string, any][])
.filter(([n, b]) => n === this.state.activeBlockInfo.name)
.sort(([n1, ds1], [n2, ds2]) => n1 > n2 ? 1 : -1)
.map(([name, blk], i) =>
......@@ -1069,7 +1058,7 @@ export class ExperimentEditor extends React.Component<Props, State> {
<FormGroup>
<h3>Analyzers</h3>
{
(Object.entries(this.state.cache.contents.analyzers): [string, any][])
(Object.entries(this.props.data.contents.analyzers): [string, any][])
.filter(([n, b]) => n === this.state.activeBlockInfo.name)
.sort(([n1, ds1], [n2, ds2]) => n1 > n2 ? 1 : -1)
.map(([name, blk], i) =>
......@@ -1092,15 +1081,15 @@ export class ExperimentEditor extends React.Component<Props, State> {
</InfoTooltip>
</h3>
<EnvironmentConfig
envInfo={this.state.cache.contents.globals.environment}
queue={this.state.cache.contents.globals.queue}
envInfo={this.props.data.contents.globals.environment}
queue={this.props.data.contents.globals.queue}
availableEnvs={this.props.environments}
updateEnvInfo={(name, version) => this.setContents({
...this.state.cache.contents,
...this.props.data.contents,
globals: {
...this.state.cache.contents.globals,
...this.props.data.contents.globals,
environment: {
...this.state.cache.contents.globals.environment,
...this.props.data.contents.globals.environment,
name,
version,
},
......@@ -1108,9 +1097,9 @@ export class ExperimentEditor extends React.Component<Props, State> {
}
})}
updateQueue={queue => this.setContents({
...this.state.cache.contents,
...this.props.data.contents,
globals: {
...this.state.cache.contents.globals,
...this.props.data.contents.globals,
queue,
}
})}
......@@ -1131,17 +1120,17 @@ export class ExperimentEditor extends React.Component<Props, State> {
<ParameterConsume
name={`${ algName }/${ pName }`}
parameter={paramObj}
currVal={this.state.cache.contents.globals[algName][pName]}
currVal={this.props.data.contents.globals[algName][pName]}
updateFunc={(val: ParameterValue) => {
const globals = {
...this.state.cache.contents.globals,
...this.props.data.contents.globals,
[algName]: {
...this.state.cache.contents.globals[algName],
...this.props.data.contents.globals[algName],
[pName]: val
}
};
this.setContents({
...this.state.cache.contents,
...this.props.data.contents,
globals
});
}}
......@@ -1161,11 +1150,11 @@ export class ExperimentEditor extends React.Component<Props, State> {
const {representation, blocks, datasets, analyzers, connections} = tc.contents;
const groups = tc.extraContents ? tc.extraContents.groups : [];
const expData = [
...Object.entries(this.state.cache.contents.datasets)
...Object.entries(this.props.data.contents.datasets)
.map(([name, ds]) => ({ [name]: datasetFieldToString(ds) })),
...Object.entries(this.state.cache.contents.blocks)
...Object.entries(this.props.data.contents.blocks)
.map(([name, b]) => ({ [name]: b.algorithm })),
...Object.entries(this.state.cache.contents.analyzers)
...Object.entries(this.props.data.contents.analyzers)
.map(([name, a]) => ({ [name]: a.algorithm })),
].reduce((o, data) => ({ ...o, ...data }), {});
const errorMap = this.getErrorMap();
......@@ -1238,9 +1227,9 @@ export class ExperimentEditor extends React.Component<Props, State> {
className='mx-auto'
outline
color='secondary'
onClick={() => this.props.saveFunc(this.state.cache)}
onClick={() => this.props.saveFunc(this.props.data)}
>
Save Changes (Changes are <ValidSchemaBadge entity='experiment' obj={this.state.cache} />)
Save Changes (Changes are <ValidSchemaBadge entity='experiment' obj={this.props.data} />)
</Button>
</div>
<Form onSubmit={(e) => e.preventDefault()}>
......@@ -1251,8 +1240,8 @@ export class ExperimentEditor extends React.Component<Props, State> {
type='text'
className='expDescription'
placeholder='Experiment description...'
value={this.state.cache.contents['description']}
onChange={e => this.setContents({ ...this.state.cache.contents, 'description': e.target.value})}
value={this.props.data.contents['description']}
onChange={e => this.setContents({ ...this.props.data.contents, 'description': e.target.value})}
/>
</FormGroup>
</FormGroup>
......@@ -1289,12 +1278,17 @@ const mapStateToProps = (state, ownProps) => {
datasets: [],
}
};
const exps = Selectors.experimentGet(state);
const normalBlocks = Selectors.normalBlocks(state);
const analyzerBlocks = Selectors.analyzerBlocks(state);
const obj = {
experiments: Selectors.experimentGet(state),
experiments: exps,
data: exps[ownProps.index] || getValidObj({name: '', contents: {}}, tc, [normalBlocks, ...analyzerBlocks]),
// algorithms for normal blocks
normalBlocks: Selectors.normalBlocks(state),
normalBlocks,
// algorithms for analyzer blocks
analyzerBlocks: Selectors.analyzerBlocks(state),
analyzerBlocks,
datasets: Selectors.flattenedDatabases(state),
toolchain: tc,
environments: Selectors.environmentsGet(state),
......@@ -1302,4 +1296,12 @@ const mapStateToProps = (state, ownProps) => {
return obj;
};
export default connect(mapStateToProps)(ExperimentEditor);
const mapDispatchToProps = (dispatch, ownProps) => ({
// replace the obj in the Redux store with the new object
updateFunc: (obj) => {
console.log(`dispatching for ${ obj.name }`);
dispatch(Actions[`experimentUpdate`](obj.name, obj));
},
});
export default connect(mapStateToProps, mapDispatchToProps)(ExperimentEditor);
......@@ -7,6 +7,7 @@ import { spies } from '@test';
import * as Selectors from '@store/selectors.js';
import { ExperimentEditor as C } from '.';
import { getValidExperimentObj as getValidObj } from '@helpers/beat';
import testAlgs from '@test/test_algs.json';
import testDbs from '@test/test_dbs.json';
......@@ -40,7 +41,10 @@ describe('<ExperimentEditor />', () => {
].concat(testExps);
exps.forEach(function(exp){
if(exp.name !== 'user/user/single/1/single_add')
return;
const saveFunc = () => {};
const updateFunc = () => {};
const tc = testTcs.find(tc => exp.name.includes(tc.name));
it(`${ exp.name }`, () => {
......@@ -54,11 +58,12 @@ describe('<ExperimentEditor />', () => {
toolchain={tc}
saveFunc={saveFunc}
environments={envs}
updateFunc={updateFunc}
/>
);
expect(wrapper).to.have.props(
['data', 'experiments', 'normalBlocks', 'analyzerBlocks', 'datasets', 'toolchain', 'saveFunc', 'environments']
['data', 'experiments', 'normalBlocks', 'analyzerBlocks', 'datasets', 'toolchain', 'saveFunc', 'environments', 'updateFunc']
);
});
});
......@@ -68,10 +73,14 @@ describe('<ExperimentEditor />', () => {
it(`user/user/single/1/single_add`, () => {
const expName = 'user/user/single/1/single_add';
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={{name: expName, contents: {}}}
data={getValidObj({name: expName, contents: {}}, tc, [normalBlocks, ...analyzerBlocks])}
experiments={[]}
normalBlocks={normalBlocks}
analyzerBlocks={analyzerBlocks}
......@@ -79,17 +88,19 @@ describe('<ExperimentEditor />', () => {
toolchain={tc}
saveFunc={saveFunc}
environments={envs}
updateFunc={updateFunc}
/>
);
//console.log('doing name');
expect(wrapper.state('cache')).to.have.property('name', expName);
expect(wrapper.props().data).to.have.property('name', expName);
//console.log('finished name change, doing dataset');
wrapper.find('div.dataset0 select').simulate('change', { target: { value: 'protocol/set (simple/1)'}});
expect(wrapper.state('cache').contents).to.have.deep.property('datasets', {
expect(updateFunc.callCount).to.equal(1);
expect(wrapper.props().data.contents).to.have.deep.property('datasets', {
'set': {
'set': 'set',
'protocol': 'protocol',
......@@ -102,7 +113,8 @@ describe('<ExperimentEditor />', () => {
wrapper.find('svg #block_echo').simulate('click');
expect(wrapper.find({ name: 'echo', set: 'blocks'}).find('.tcBlockBackground').prop('className')).to.include('highlighted');
wrapper.find('div.block0 div.algorithm select').at(0).simulate('change', { target: { value: 'user/integers_add/1'}});
expect(wrapper.state('cache').contents).to.have.deep.property('blocks', {
expect(updateFunc.callCount).to.equal(2);
expect(wrapper.props().data.contents).to.have.deep.property('blocks', {
'echo': {
'inputs': {
'in_data': 'in'
......@@ -120,7 +132,8 @@ describe('<ExperimentEditor />', () => {
wrapper.find('svg #block_analysis').simulate('click');
expect(wrapper.find({ name: 'analysis', set: 'analyzers'}).find('.tcBlockBackground').prop('className')).to.include('highlighted');
wrapper.find('div.block0 div.algorithm select').at(0).simulate('change', { target: { value: 'user/integers_echo_analyzer/1'}});
expect(wrapper.state('cache').contents).to.have.deep.property('analyzers', {
expect(updateFunc.callCount).to.equal(3);
expect(wrapper.props().data.contents).to.have.deep.property('analyzers', {
'analysis': {
'inputs': {
'in_data': 'in'
......@@ -133,8 +146,10 @@ describe('<ExperimentEditor />', () => {
//console.log('finished analyzer, doing env');
wrapper.find('.globals select.env').simulate('change', { target: { value: 'Scientific Python 2.7 (1.0.0)'}});
expect(updateFunc.callCount).to.equal(4);
wrapper.find('.globals select.queue').simulate('change', { target: { value: 'Default'}});
expect(wrapper.state('cache').contents).to.have.deep.property('globals', {
expect(updateFunc.callCount).to.equal(5);
expect(wrapper.props().data.contents).to.have.deep.property('globals', {
'queue': 'Default',
'environment': {
'version': '1.0.0',
......@@ -147,7 +162,7 @@ describe('<ExperimentEditor />', () => {
//console.log('finished env, doing cache check');
expect(wrapper.state('cache')).to.be.deep.equal({
expect(wrapper.props().data).to.be.deep.equal({
'name': 'user/user/single/1/single_add',
'contents': {
description: '',
......
......@@ -457,6 +457,9 @@
"environment": {
"version": "1.2.0",
"name": "Python 2.7"
},
"user/integers_add/1": {
"offset": 2
}
}
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment