Skip to content
Snippets Groups Projects
Commit 37cd0744 authored by Flavio TARSETTI's avatar Flavio TARSETTI
Browse files

Merge branch 'fix_issues_plotter_editor' into 'dev'

Fix issues plotter editor

See merge request !23
parents 134e1a10 586efa0b
No related branches found
No related tags found
2 merge requests!29Fix issues for plotter editor and added unit tests,!23Fix issues plotter editor
Pipeline #24715 passed
......@@ -51,6 +51,19 @@ export default class ParameterCreate extends React.Component<Props, State> {
const params = this.props.params;
const updateParameter = this.props.updateParameter;
let new_param = false;
if (param.type == 'bool')
{
if(typeof(param.default) == 'string')
{
new_param = true;
}
else
{
new_param = false;
}
}
return (
<React.Fragment>
<TypedField
......@@ -332,6 +345,12 @@ export default class ParameterCreate extends React.Component<Props, State> {
<Input
name={`default${ name }`}
type='radio'
checked={param.default && !new_param}
value={true}
onChange={(e) => updateParameter(name, {
...param,
default: JSON.parse(e.target.value)
})}
/>
True
</Label>
......@@ -341,6 +360,12 @@ export default class ParameterCreate extends React.Component<Props, State> {
<Input
name={`default${ name }`}
type='radio'
checked={!param.default && !new_param}
value={false}
onChange={(e) => updateParameter(name, {
...param,
default: JSON.parse(e.target.value)
})}
/>
False
</Label>
......
//// @flow
//import React from 'react';
//import { expect } from 'chai';
//import { mount } from 'enzyme';
//import sinon from 'sinon';
//import { spies } from '@test';
//
//import C from './ParameterCreate.jsx';
//
//describe.only('<ParameterCreate />', () => {
// let wrapper;
// const name = 'parameter';
// // shortcut for a func to update just the parameter obj, not the name
// const _updateParameter = (name, p, oldName) => {
// wrapper.setProps({
// param: p,
// });
// };
// const params = [];
//
// afterEach(() => {
// if(wrapper && wrapper.unmount)
// wrapper.unmount();
// });
//
// it(`saves the default value of a boolean parameter`, () => {
// let param = {
// type: '',
// default: '',
// description: '',
// };
// const updateParameter = sinon.spy(_updateParameter);
//
// wrapper = mount(
// <C
// name={name}
// param={param}
// params={params}
// updateParameter={updateParameter}
// />
// );
//
// // sanity checks
// expect(wrapper.find('TypedField')).to.have.prop('name', name);
//
// wrapper.find('Input.custom-select').prop('onChange')( { target: { value: 'bool' }});
// wrapper.update();
// console.log(wrapper.props().param);
// expect(wrapper.props().param).to.deep.equal(
// {
// type: 'bool',
// default: '',
// description: '',
// }
// );
//
// // wrapper.find('input[type="radio"]').at(0).simulate('change', { target: { checked: 'true' }});;
// wrapper.find('input[type="radio"]').at(0).simulate('change', { target: { value: 'true' }});;
// wrapper.update();
// expect(updateParameter.args[0]).to.deep.equal([
// name,
// {
// type: 'bool',
// default: 'true',
// description: '',
// }
// ]);
// });
//});
......@@ -86,6 +86,81 @@ export class PlotterEditor extends React.Component<Props, State> {
this.setContents({ parameters: params });
}
// helper to change a value in the "contents" subobject of a plotter
// (this is where the vast majority of change happens)
changeContentsVal = (field: string, val: any) => {
const newCache = {
...this.props.data,
contents: {
...this.props.data.contents,
[field]: val
}
};
this.props.updateFunc(newCache);
}
renderLibraries = () => (
<TabPane tabId='2'>
<Row>
<Col sm='12'>
{
// loop through all the used libs
(Object.entries(this.props.data.contents.uses): [string, any][])
.map(([name, lib], i, lEntries) => (
<TypedField
key={i}
name={name}
type={lib}
types={this.props.libraries.map(l => l.name)}
existingFields={lEntries.map(([n, v]) => n)}
nameUpdateFunc={str => {
// update the alias
this.changeContentsVal('uses',
changeObjFieldName(
this.props.data.contents.uses,
name,
str
)
);
}}
typeUpdateFunc={str => {
// update the chosen library for the alias
const libs = {
...this.props.data.contents.uses,
[name]: str
};
this.changeContentsVal('uses', libs);
}}
deleteFunc={() => {
const libs = { ...this.props.data.contents.uses };
delete libs[name];
this.changeContentsVal('uses', libs);
}}
/>
))
}
</Col>
</Row>
<Button outline block
id='newLibraryBtn'
onClick={() => {
const newKey = generateNewKey('library', Object.keys(this.props.data.contents.uses));
this.changeContentsVal('uses',
{
...this.props.data.contents.uses,
[newKey]: ''
}
);
}}
>
New Library
</Button>
</TabPane>
);
render = () => {
return (
<div>
......@@ -131,7 +206,11 @@ export class PlotterEditor extends React.Component<Props, State> {
}
</Input>
</FormGroup>
<FormGroup>
<FormGroup id='usedLibs'>
<h5>Libraries</h5>
{ this.renderLibraries() }
</FormGroup>
<FormGroup id='paramsForm'>
<h5>Parameters</h5>
{
(Object.entries(this.props.data.contents.parameters): [string, any][]).map(([name, param], i, params) => (
......@@ -178,6 +257,12 @@ const mapStateToProps = (state, ownProps) => {
plotDfNames: Selectors.dataformatGet(state).map(df => df.name).filter(df => df.startsWith('plot/')),
data: plotters[ownProps.index] || getValidObj(),
};
if(obj.data.contents.dataformat === "")
{
obj.plotDfNames.unshift("-- select an option --")
};
return obj;
};
......
// @flow
import React from 'react';
import { expect } from 'chai';
import { mount } from 'enzyme';
import sinon from 'sinon';
import { spies } from '@test';
import { PlotterEditor as C } from '.';
import { getValidPlotterObj as getValidObj } from '@helpers/beat';
import testPlotters from '@test/test_plotters.json';
import testDfs from '@test/test_dfs.json';
import testLibs from '@test/test_libs.json';
const libs = [];
describe('<PlotterEditor />', () => {
let wrapper;
afterEach(() => {
if(wrapper && wrapper.unmount)
wrapper.unmount();
});
describe('accepts', () => {
const plots = [
{
name: 'test/myplot/1',
contents: {
description: 'A basic plotter as a sanity test for the PlotterEditor',
language: 'python',
parameters: {
'parameter_1': {
type: 'string',
default: 'default',
description: 'description',
}
},
dataformat: "plot/bar/1",
uses: {
'library': 'plot/baselib/1',
}
}
}
].concat(testPlotters.map(a => getValidObj(a)));
plots.forEach(function(plot){
const saveFunc = () => {};
const updateFunc = () => {};
const dfs = testDfs.filter(d => d.name.startsWith('plot')).map(testDfs => testDfs.name);
it(`${ plot.name }`, () => {
wrapper = mount(
<C
data={getValidObj(plot)}
plotters={plots}
dataformats={plot.contents.dataformat}
plotDfNames={dfs}
libraries={libs}
saveFunc={saveFunc}
updateFunc={updateFunc}
/>
);
expect(wrapper).to.have.props(
['data', 'plotters', 'dataformats', 'libraries', 'saveFunc', 'updateFunc']
)
});
});
});
describe('creates', () => {
it('test/createplot/1', () => {
const saveFunc = sinon.spy();
const _updateFunc = (obj) => {
wrapper.setProps && wrapper.setProps({ data: obj });
};
const updateFunc = sinon.spy(_updateFunc);
const plotName = 'test/createplot/1';
const dfs = testDfs.filter(d => d.name.startsWith('plot')).map(testDfs => testDfs.name);
wrapper = mount(
<C
data={getValidObj({name: plotName, contents: {}})}
plotters={[]}
dataformats={testDfs}
plotDfNames={dfs}
libraries={testLibs}
saveFunc={saveFunc}
updateFunc={updateFunc}
/>
);
expect(wrapper).to.have.props(
['data', 'plotters', 'dataformats', 'plotDfNames', 'libraries', 'saveFunc', 'updateFunc']
);
expect(wrapper.props().data).to.have.property('name', plotName);
expect(wrapper.props().data.contents).to.have.property('dataformat', '');
expect(wrapper.props().data.contents).to.have.property('dataformat').with.lengthOf(0);
expect(wrapper.props().data.contents).to.have.property('language').with.lengthOf(6);
expect(wrapper.props().data.contents).to.have.property('language', 'python');
wrapper.find('#plDf select').simulate('change', { target: { value: 'user/anotherdataformat/1'}});
expect(updateFunc.callCount).to.equal(1);
expect(wrapper.props().data.contents).to.have.property('dataformat', 'user/anotherdataformat/1');
expect(wrapper.props().data.contents).to.have.property('language', 'python');
expect(wrapper.props().data.contents).to.have.property('description').with.lengthOf(0);
expect(wrapper.props().data.contents).to.have.property('language').with.lengthOf(6);
expect(wrapper.props().data.contents).to.have.property('dataformat').with.lengthOf(24);
expect(wrapper.props().data.contents).to.have.property('parameters').that.deep.equals({});
expect(wrapper.props().data.contents).to.have.property('uses').that.deep.equals({});
expect(wrapper.props().data.contents).to.deep.equal({
'description': '',
'uses': {},
'parameters': {},
'language': 'python',
'dataformat': 'user/anotherdataformat/1'
});
});
it('test/plot_add_lib/1', () => {
const saveFunc = sinon.spy();
const _updateFunc = (obj) => {
wrapper.setProps && wrapper.setProps({ data: obj });
};
const updateFunc = sinon.spy(_updateFunc);
const plotName = 'test/plot_other/1';
const dfs = testDfs.filter(d => d.name.startsWith('plot')).map(testDfs => testDfs.name);
wrapper = mount(
<C
data={getValidObj({name: plotName, contents: {}})}
plotters={[]}
dataformats={testDfs}
plotDfNames={dfs}
libraries={testLibs}
saveFunc={saveFunc}
updateFunc={updateFunc}
/>
);
expect(wrapper).to.have.props(
['data', 'plotters', 'dataformats', 'plotDfNames', 'libraries', 'saveFunc', 'updateFunc']
);
expect(wrapper.props().data).to.have.property('name', plotName);
expect(wrapper.props().data.contents).to.have.property('dataformat', '');
expect(wrapper.props().data.contents).to.have.property('dataformat').with.lengthOf(0);
expect(wrapper.props().data.contents).to.have.property('language').with.lengthOf(6);
expect(wrapper.props().data.contents).to.have.property('language', 'python');
expect(wrapper.props().data.contents).to.have.property('parameters').that.deep.equals({});
expect(wrapper.props().data.contents).to.have.property('uses').that.deep.equals({});
wrapper.find('button#newLibraryBtn').simulate('click');
wrapper.find('#usedLibs CacheInput[type="text"]').prop('onChange')({ target: { value: 'lib'}});
expect(updateFunc.callCount).to.equal(2);
wrapper.find('#usedLibs select').simulate('change', { target: { value: 'user/anotherlib/1'}});
expect(updateFunc.callCount).to.equal(3);
expect(wrapper.props().data.contents).to.deep.equal({
'description': '',
'uses': {
lib: 'user/anotherlib/1'
},
'language': 'python',
'parameters': {},
'dataformat': ''
});
});
it('test/plot_add_param/1', () => {
const saveFunc = sinon.spy();
const _updateFunc = (obj) => {
wrapper.setProps && wrapper.setProps({ data: obj });
};
const updateFunc = sinon.spy(_updateFunc);
const plotName = 'test/plot_param/1';
const dfs = testDfs.filter(d => d.name.startsWith('plot')).map(testDfs => testDfs.name);
wrapper = mount(
<C
data={getValidObj({name: plotName, contents: {}})}
plotters={[]}
dataformats={testDfs}
plotDfNames={dfs}
libraries={testLibs}
saveFunc={saveFunc}
updateFunc={updateFunc}
/>
);
expect(wrapper).to.have.props(
['data', 'plotters', 'dataformats', 'plotDfNames', 'libraries', 'saveFunc', 'updateFunc']
);
expect(wrapper.props().data).to.have.property('name', plotName);
expect(wrapper.props().data.contents).to.have.property('dataformat', '');
expect(wrapper.props().data.contents).to.have.property('dataformat').with.lengthOf(0);
expect(wrapper.props().data.contents).to.have.property('language').with.lengthOf(6);
expect(wrapper.props().data.contents).to.have.property('language', 'python');
expect(wrapper.props().data.contents).to.have.property('parameters').that.deep.equals({});
expect(wrapper.props().data.contents).to.have.property('uses').that.deep.equals({});
wrapper.find('button#newParameterBtn').simulate('click');
expect(updateFunc.callCount).to.equal(1);
wrapper.find('CacheInput[placeholder="Parameter name..."]').prop('onChange')( { target: { value: 'offset' }});
expect(updateFunc.callCount).to.equal(2);
wrapper.find('#paramsForm select').prop('onChange')( { target: { value: 'int32' }});
expect(updateFunc.callCount).to.equal(3);
wrapper.find('input[placeholder="Default"]').simulate('change', { target: { value: '1' }});
expect(updateFunc.callCount).to.equal(4);
expect(wrapper.props().data.contents.parameters).to.have.deep.property('offset', {
'default': '1',
'type': 'int32',
'description': '',
});
expect(wrapper.props().data.contents).to.deep.equal({
'description': '',
'uses': {},
'language': 'python',
'parameters': {
'offset': {
'default': '1',
'type': 'int32',
description: '',
}
},
'dataformat': ''
});
});
});
});
......@@ -172,6 +172,13 @@ const mapStateToProps = (state, ownProps) => {
plotters: Selectors.plotterGet(state),
data: params[ownProps.index] || getValidObj(),
};
if(!obj.data.contents.hasOwnProperty('plotter') ||
obj.data.contents.plotters === "")
{
obj.plotters.unshift({name: "-- select an option --"})
};
return obj;
};
......
......@@ -16,5 +16,6 @@
"data": {
"$ref": "experiment#/definitions/parameter_set"
}
}
},
"required": ["plotter"]
}
[
{
"name": "plot/bar/1",
"contents": {
"description": "Basic bar plotter for simple histograms",
"language": "python",
"parameters": {
"axis-fontsize": {
"default": 10,
"description": "Controls the axis font size (labels and values)",
"type": "uint16"
},
"bar-alpha": {
"default": 0.75,
"description": "Value for the alpha effect in the bar plot",
"type": "float64"
},
"bar-norm": {
"default": true,
"description": "If set to true will normalize the distribution between 0-1",
"type": "bool"
},
"bar_attributes": {
"default": "",
"description": "Bar attributes passed directly to Matplotlib",
"type": "string"
},
"content_type": {
"choice": [
"image/png",
"image/jpeg",
"application/pdf"
],
"default": "image/png",
"description": "The type of image returned",
"type": "string"
},
"dpi": {
"default": 60,
"description": "Dots-per-inch in raster image formats",
"type": "uint16"
},
"grid": {
"default": false,
"description": "If we should draw grid lines or not for the plot",
"type": "bool"
},
"height": {
"default": 300,
"description": "Height of the resulting image in pixels",
"type": "uint16"
},
"legend": {
"default": "",
"description": "Short description of the data, to be added to the plot",
"type": "string"
},
"legend-bbox-to-anchor": {
"default": "1.0&1.0",
"description": "Specify any arbitrary location for the legend ",
"type": "string"
},
"legend-fontsize": {
"default": 12,
"description": "Controls the font size of the legend",
"type": "uint16"
},
"legend-loc": {
"default": "best",
"description": "The location of the legend",
"type": "string"
},
"title": {
"default": "Bar plot",
"description": "The title for this plot",
"type": "string"
},
"title-fontsize": {
"default": 10,
"description": "Controls the title font size",
"type": "uint16"
},
"width": {
"default": 400,
"description": "Width of the resulting image in pixels",
"type": "uint16"
},
"xaxis_multiplier": {
"default": 1,
"description": "The multiplication factor for the X-axis (horizontal)",
"type": "float64"
},
"xlabel": {
"default": "",
"description": "The label of the X-axis (horizontal)",
"type": "string"
},
"yaxis_log": {
"default": false,
"description": "If Y-axis (vertical) should be in log-scale",
"type": "bool"
},
"yaxis_multiplier": {
"default": 1,
"description": "The multiplication factor for the Y-axis (vertical)",
"type": "float64"
},
"ylabel": {
"default": "",
"description": "The label of the Y-axis (vertical)",
"type": "string"
}
},
"dataformat": "plot/bar/1",
"uses": {
"baselib": "plot/baselib/1"
}
}
},
{
"name": "plot/isoroc/1",
"contents": {
"description": "ROC/DET plotter following the ISO/IEC 19795-1:2006(E) standard",
"language": "python",
"parameters": {
"axis-fontsize": {
"default": 10,
"description": "Controls the axis font size (labels and values)",
"type": "uint16"
},
"content_type": {
"choice": [
"image/png",
"image/jpeg",
"application/pdf"
],
"default": "image/png",
"description": "The type of image returned",
"type": "string"
},
"det": {
"default": false,
"description": "If set, plot a DET curve instead of a ROC",
"type": "bool"
},
"dpi": {
"default": 60,
"description": "Dots-per-inch in raster image formats",
"type": "uint16"
},
"grid": {
"default": true,
"description": "If we should draw grid lines or not for the plot",
"type": "bool"
},
"height": {
"default": 300,
"description": "Height of the resulting image in pixels",
"type": "uint16"
},
"legend": {
"default": "",
"description": "Short description of the data, to be added to the plot",
"type": "string"
},
"legend-fontsize": {
"default": 12,
"description": "Controls the font size of the legend",
"type": "uint16"
},
"legend-loc": {
"default": "best",
"description": "The location of the legend",
"type": "string"
},
"line_attributes": {
"default": "",
"description": "Scatter/Line attributes passed directly to Matplotlib",
"type": "string"
},
"title": {
"default": "ISO/IEC 19795-1:2006(E) ROC",
"description": "The title for this plot",
"type": "string"
},
"title-fontsize": {
"default": 10,
"description": "Controls the title font size",
"type": "uint16"
},
"width": {
"default": 400,
"description": "Width of the resulting image in pixels",
"type": "uint16"
},
"xaxis_log": {
"default": false,
"description": "If X-axis (horizontal) should be in log-scale",
"type": "bool"
},
"xaxis_multiplier": {
"default": "100.0",
"description": "The multiplication factor for the X-axis (horizontal)",
"type": "float64"
},
"xlabel": {
"default": "False Positives (False Match Rate), in %",
"description": "The label of the X-axis (horizontal)",
"type": "string"
},
"xlim-left": {
"default": "0.0",
"description": "",
"type": "float64"
},
"xlim-right": {
"default": "100.0",
"description": "",
"type": "float64"
},
"yaxis_log": {
"default": false,
"description": "If Y-axis (vertical) should be in log-scale",
"type": "bool"
},
"yaxis_multiplier": {
"default": "100.0",
"description": "The multiplication factor for the Y-axis (vertical)",
"type": "float64"
},
"ylabel": {
"default": "True Positives (1 - False Non-Match Rate), in %",
"description": "The label of the Y-axis (vertical)",
"type": "string"
},
"ylim-bottom": {
"default": "0.0",
"description": "",
"type": "float64"
},
"ylim-top": {
"default": "100.0",
"description": "",
"type": "float64"
}
},
"dataformat": "plot/isoroc/1",
"uses": {
"baselib": "plot/baselib/1"
}
}
},
{
"name": "plot/scatter/1",
"contents": {
"description": "Basic scatter plotter for simple lines",
"language": "python",
"parameters": {
"content_type": {
"choice": [
"image/png",
"image/jpeg",
"application/pdf"
],
"default": "image/png",
"description": "The type of image returned",
"type": "string"
},
"dpi": {
"default": 60,
"description": "Dots-per-inch in raster image formats",
"type": "uint16"
},
"grid": {
"default": false,
"description": "If we should draw grid lines or not for the plot",
"type": "bool"
},
"height": {
"default": 300,
"description": "Height of the resulting image in pixels",
"type": "uint16"
},
"legend": {
"default": "",
"description": "Short description of the data, to be added to the plot",
"type": "string"
},
"line_attributes": {
"default": "",
"description": "Scatter/Line attributes passed directly to Matplotlib",
"type": "string"
},
"title": {
"default": "Scatter plot",
"description": "The title for this plot",
"type": "string"
},
"width": {
"default": 400,
"description": "Width of the resulting image in pixels",
"type": "uint16"
},
"xaxis_log": {
"default": false,
"description": "If X-axis (horizontal) should be in log-scale",
"type": "bool"
},
"xaxis_multiplier": {
"default": "1.0",
"description": "The multiplication factor for the X-axis (horizontal)",
"type": "float64"
},
"xlabel": {
"default": "X",
"description": "The label of the X-axis (horizontal)",
"type": "string"
},
"yaxis_log": {
"default": false,
"description": "If Y-axis (vertical) should be in log-scale",
"type": "bool"
},
"yaxis_multiplier": {
"default": "1.0",
"description": "The multiplication factor for the Y-axis (vertical)",
"type": "float64"
},
"ylabel": {
"default": "Y",
"description": "The label of the Y-axis (vertical)",
"type": "string"
}
},
"dataformat": "plot/scatter/1",
"uses": {
"baselib": "plot/baselib/1"
}
}
}
]
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment