Unverified Commit 9ce751de authored by SparkSnail's avatar SparkSnail Committed by GitHub
Browse files

Merge pull request #216 from microsoft/master

merge master
parents d16dbe9a cb52d441
...@@ -80,8 +80,6 @@ class MsgDispatcherTestCase(TestCase): ...@@ -80,8 +80,6 @@ class MsgDispatcherTestCase(TestCase):
send(CommandType.ReportMetricData, '{"parameter_id":0,"type":"PERIODICAL","value":10}') send(CommandType.ReportMetricData, '{"parameter_id":0,"type":"PERIODICAL","value":10}')
send(CommandType.ReportMetricData, '{"parameter_id":1,"type":"FINAL","value":11}') send(CommandType.ReportMetricData, '{"parameter_id":1,"type":"FINAL","value":11}')
send(CommandType.UpdateSearchSpace, '{"name":"SS0"}') send(CommandType.UpdateSearchSpace, '{"name":"SS0"}')
send(CommandType.AddCustomizedTrialJob, '{"param":-1}')
send(CommandType.ReportMetricData, '{"parameter_id":2,"type":"FINAL","value":22}')
send(CommandType.RequestTrialJobs, '1') send(CommandType.RequestTrialJobs, '1')
send(CommandType.KillTrialJob, 'null') send(CommandType.KillTrialJob, 'null')
_restore_io() _restore_io()
...@@ -99,14 +97,7 @@ class MsgDispatcherTestCase(TestCase): ...@@ -99,14 +97,7 @@ class MsgDispatcherTestCase(TestCase):
self._assert_params(0, 2, [], None) self._assert_params(0, 2, [], None)
self._assert_params(1, 4, [], None) self._assert_params(1, 4, [], None)
command, data = receive() # this one is customized self._assert_params(2, 6, [[1, 4, 11, False]], {'name': 'SS0'})
data = json.loads(data)
self.assertIs(command, CommandType.NewTrialJob)
self.assertEqual(data['parameter_id'], 2)
self.assertEqual(data['parameter_source'], 'customized')
self.assertEqual(data['parameters'], {'param': -1})
self._assert_params(3, 6, [[1, 4, 11, False], [2, -1, 22, True]], {'name': 'SS0'})
self.assertEqual(len(_out_buf.read()), 0) # no more commands self.assertEqual(len(_out_buf.read()), 0) # no more commands
......
import * as React from 'react';
import axios from 'axios';
import { Row, Col, Input, Modal, Form, Button, Icon } from 'antd';
import { MANAGER_IP } from '../../static/const';
import { EXPERIMENT, TRIALS } from '../../static/datamodel';
import { FormComponentProps } from 'antd/lib/form';
const FormItem = Form.Item;
import './customized.scss';
interface CustomizeProps extends FormComponentProps {
visible: boolean;
copyTrialId: string;
closeCustomizeModal: () => void;
}
interface CustomizeState {
isShowSubmitSucceed: boolean;
isShowSubmitFailed: boolean;
isShowWarning: boolean;
searchSpace: object;
copyTrialParameter: object; // user click the trial's parameters
customParameters: object; // customized trial, maybe user change trial's parameters
customID: number; // submit customized trial succeed, return the new customized trial id
}
class Customize extends React.Component<CustomizeProps, CustomizeState> {
constructor(props: CustomizeProps) {
super(props);
this.state = {
isShowSubmitSucceed: false,
isShowSubmitFailed: false,
isShowWarning: false,
searchSpace: EXPERIMENT.searchSpace,
copyTrialParameter: {},
customParameters: {},
customID: NaN
};
}
// [submit click] user add a new trial [submit a trial]
addNewTrial = () => {
const { searchSpace, copyTrialParameter } = this.state;
// get user edited hyperParameter, ps: will change data type if you modify the input val
const customized = this.props.form.getFieldsValue();
// true: parameters are wrong
let flag = false;
Object.keys(customized).map(item => {
if (item !== 'tag') {
// unified data type
if (typeof copyTrialParameter[item] === 'number' && typeof customized[item] === 'string') {
customized[item] = JSON.parse(customized[item]);
}
if (searchSpace[item]._type === 'choice') {
if (searchSpace[item]._value.find((val: string | number) =>
val === customized[item]) === undefined) {
flag = true;
return;
}
} else {
if (customized[item] < searchSpace[item]._value[0]
|| customized[item] > searchSpace[item]._value[1]) {
flag = true;
return;
}
}
}
});
if (flag !== false) {
// open the warning modal
this.setState(() => ({ isShowWarning: true, customParameters: customized }));
} else {
// submit a customized job
this.submitCustomize(customized);
}
}
warningConfirm = () => {
this.setState(() => ({ isShowWarning: false }));
const { customParameters } = this.state;
this.submitCustomize(customParameters);
}
warningCancel = () => {
this.setState(() => ({ isShowWarning: false }));
}
submitCustomize = (customized: Object) => {
// delete `tag` key
for (let i in customized) {
if (i === 'tag') {
delete customized[i];
}
}
axios(`${MANAGER_IP}/trial-jobs`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: customized
})
.then(res => {
if (res.status === 200) {
this.setState(() => ({ isShowSubmitSucceed: true, customID: res.data.sequenceId }));
this.props.closeCustomizeModal();
} else {
this.setState(() => ({ isShowSubmitFailed: true }));
}
})
.catch(error => {
this.setState(() => ({ isShowSubmitFailed: true }));
});
}
closeSucceedHint = () => {
// also close customized trial modal
this.setState(() => ({ isShowSubmitSucceed: false }));
this.props.closeCustomizeModal();
}
closeFailedHint = () => {
// also close customized trial modal
this.setState(() => ({ isShowSubmitFailed: false }));
this.props.closeCustomizeModal();
}
componentDidMount() {
const { copyTrialId } = this.props;
if (copyTrialId !== undefined && TRIALS.getTrial(copyTrialId) !== undefined) {
const originCopyTrialPara = TRIALS.getTrial(copyTrialId).description.parameters;
this.setState(() => ({ copyTrialParameter: originCopyTrialPara }));
}
}
componentWillReceiveProps(nextProps: CustomizeProps) {
const { copyTrialId } = nextProps;
if (copyTrialId !== undefined && TRIALS.getTrial(copyTrialId) !== undefined) {
const originCopyTrialPara = TRIALS.getTrial(copyTrialId).description.parameters;
this.setState(() => ({ copyTrialParameter: originCopyTrialPara }));
}
}
render() {
const { closeCustomizeModal, visible } = this.props;
const { isShowSubmitSucceed, isShowSubmitFailed, isShowWarning, customID, copyTrialParameter } = this.state;
const {
form: { getFieldDecorator },
// form: { getFieldDecorator, getFieldValue },
} = this.props;
const warning = 'The parameters you set are not in our search space, this may cause the tuner to crash, Are'
+ ' you sure you want to continue submitting?';
return (
<Row>
{/* form: search space */}
<Modal
title="Customized trial setting"
visible={visible}
onCancel={closeCustomizeModal}
footer={null}
destroyOnClose={true}
maskClosable={false}
centered={true}
>
{/* search space form */}
<Row className="hyper-box">
<Form>
{
Object.keys(copyTrialParameter).map(item => (
<Row key={item} className="hyper-form">
<Col span={9} className="title">{item}</Col>
<Col span={15} className="inputs">
<FormItem key={item} style={{ marginBottom: 0 }}>
{getFieldDecorator(item, {
initialValue: copyTrialParameter[item],
})(
<Input />
)}
</FormItem>
</Col>
</Row>
)
)
}
<Row key="tag" className="hyper-form tag-input">
<Col span={9} className="title">Tag</Col>
<Col span={15} className="inputs">
<FormItem key="tag" style={{ marginBottom: 0 }}>
{getFieldDecorator('tag', {
initialValue: 'Customized',
})(
<Input />
)}
</FormItem>
</Col>
</Row>
</Form>
</Row>
<Row className="modal-button">
<Button
type="primary"
className="tableButton distance"
onClick={this.addNewTrial}
>
Submit
</Button>
<Button
className="tableButton cancelSty"
onClick={this.props.closeCustomizeModal}
>
Cancel
</Button>
</Row>
{/* control button */}
</Modal>
{/* clone: prompt succeed or failed */}
<Modal
visible={isShowSubmitSucceed}
footer={null}
destroyOnClose={true}
maskClosable={false}
closable={false}
centered={true}
>
<Row className="resubmit">
<Row>
<h2 className="title">
<span>
<Icon type="check-circle" className="color-succ" />
<b>Submit successfully</b>
</span>
</h2>
<div className="hint">
<span>You can find your customized trial by Trial No.{customID}</span>
</div>
</Row>
<Row className="modal-button">
<Button
className="tableButton cancelSty"
onClick={this.closeSucceedHint}
>
OK
</Button>
</Row>
</Row>
</Modal>
<Modal
visible={isShowSubmitFailed}
footer={null}
destroyOnClose={true}
maskClosable={false}
closable={false}
centered={true}
>
<Row className="resubmit">
<Row>
<h2 className="title">
<span>
<Icon type="check-circle" className="color-error" />Submit Failed
</span>
</h2>
<div className="hint">
<span>Unknown error.</span>
</div>
</Row>
<Row className="modal-button">
<Button
className="tableButton cancelSty"
onClick={this.closeFailedHint}
>
OK
</Button>
</Row>
</Row>
</Modal>
{/* hyperParameter not match search space, warning modal */}
<Modal
visible={isShowWarning}
footer={null}
destroyOnClose={true}
maskClosable={false}
closable={false}
centered={true}
>
<Row className="resubmit">
<Row>
<h2 className="title">
<span>
<Icon className="color-warn" type="warning" />Warning
</span>
</h2>
<div className="hint">
<span>{warning}</span>
</div>
</Row>
<Row className="modal-button center">
<Button
className="tableButton cancelSty distance"
onClick={this.warningConfirm}
>
Confirm
</Button>
<Button
className="tableButton cancelSty"
onClick={this.warningCancel}
>
Cancel
</Button>
</Row>
</Row>
</Modal>
</Row>
);
}
}
export default Form.create<FormComponentProps>()(Customize);
\ No newline at end of file
.ant-modal-body{
border-radius: none;
}
.ant-modal-title {
font-size: 18px;
}
/* resubmit confirm modal style */
.resubmit{
.title{
font-size: 16px;
color: #000;
.color-warn, .color-error{
color: red;
}
i{
margin-right: 10px;
}
}
.hint{
padding: 15px 0;
color: #333;
margin-left: 30px;
}
.color-succ{
color: green;
}
}
.hyper-box{
padding: 16px 18px 16px 16px;
}
.hyper-form{
height: 32px;
margin-bottom: 8px;
.title{
font-size: 14px;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 32px;
}
.inputs{
height: 32px;
}
input{
height: 32px;
}
}
.tag-input{
margin-top: 25px;
}
/* submit & cancel buttons style*/
.modal-button{
text-align: right;
height: 28px;
/* cancel button style*/
.cancelSty{
width: 80px;
background-color: #dadada;
border: none;
color: #333;
}
.cancelSty:hover, .cancelSty:active, .cancelSty:focus{
background-color: #dadada;
}
.distance{
margin-right: 8px;
}
}
.center{
text-align: center;
}
...@@ -7,10 +7,11 @@ const Option = Select.Option; ...@@ -7,10 +7,11 @@ const Option = Select.Option;
const CheckboxGroup = Checkbox.Group; const CheckboxGroup = Checkbox.Group;
import { MANAGER_IP, trialJobStatus, COLUMN_INDEX, COLUMNPro } from '../../static/const'; import { MANAGER_IP, trialJobStatus, COLUMN_INDEX, COLUMNPro } from '../../static/const';
import { convertDuration, formatTimestamp, intermediateGraphOption, killJob } from '../../static/function'; import { convertDuration, formatTimestamp, intermediateGraphOption, killJob } from '../../static/function';
import { TRIALS } from '../../static/datamodel'; import { EXPERIMENT, TRIALS } from '../../static/datamodel';
import { TableRecord } from '../../static/interface'; import { TableRecord } from '../../static/interface';
import OpenRow from '../public-child/OpenRow'; import OpenRow from '../public-child/OpenRow';
import Compare from '../Modal/Compare'; import Compare from '../Modal/Compare';
import Customize from '../Modal/CustomizedTrial';
import '../../static/style/search.scss'; import '../../static/style/search.scss';
require('../../static/style/tableStatus.css'); require('../../static/style/tableStatus.css');
require('../../static/style/logPath.scss'); require('../../static/style/logPath.scss');
...@@ -45,6 +46,8 @@ interface TableListState { ...@@ -45,6 +46,8 @@ interface TableListState {
intermediateData: Array<object>; // a trial's intermediate results (include dict) intermediateData: Array<object>; // a trial's intermediate results (include dict)
intermediateId: string; intermediateId: string;
intermediateOtherKeys: Array<string>; intermediateOtherKeys: Array<string>;
isShowCustomizedModal: boolean;
copyTrialId: string; // user copy trial to submit a new customized trial
} }
interface ColumnIndex { interface ColumnIndex {
...@@ -71,7 +74,9 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -71,7 +74,9 @@ class TableList extends React.Component<TableListProps, TableListState> {
selectedRowKeys: [], // close selected trial message after modal closed selectedRowKeys: [], // close selected trial message after modal closed
intermediateData: [], intermediateData: [],
intermediateId: '', intermediateId: '',
intermediateOtherKeys: [] intermediateOtherKeys: [],
isShowCustomizedModal: false,
copyTrialId: ''
}; };
} }
...@@ -236,17 +241,36 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -236,17 +241,36 @@ class TableList extends React.Component<TableListProps, TableListState> {
this.setState({ isShowCompareModal: false, selectedRowKeys: [], selectRows: [] }); this.setState({ isShowCompareModal: false, selectedRowKeys: [], selectRows: [] });
} }
// open customized trial modal
setCustomizedTrial = (trialId: string) => {
this.setState({
isShowCustomizedModal: true,
copyTrialId: trialId
});
}
closeCustomizedTrial = () => {
this.setState({
isShowCustomizedModal: false,
copyTrialId: ''
});
}
render() { render() {
const { pageSize, columnList } = this.props; const { pageSize, columnList } = this.props;
const tableSource: Array<TableRecord> = JSON.parse(JSON.stringify(this.props.tableSource)); const tableSource: Array<TableRecord> = JSON.parse(JSON.stringify(this.props.tableSource));
const { intermediateOption, modalVisible, isShowColumn, const { intermediateOption, modalVisible, isShowColumn,
selectRows, isShowCompareModal, selectedRowKeys, intermediateOtherKeys } = this.state; selectRows, isShowCompareModal, selectedRowKeys, intermediateOtherKeys,
isShowCustomizedModal, copyTrialId
} = this.state;
const rowSelection = { const rowSelection = {
selectedRowKeys: selectedRowKeys, selectedRowKeys: selectedRowKeys,
onChange: (selected: string[] | number[], selectedRows: Array<TableRecord>) => { onChange: (selected: string[] | number[], selectedRows: Array<TableRecord>) => {
this.fillSelectedRowsTostate(selected, selectedRows); this.fillSelectedRowsTostate(selected, selectedRows);
} }
}; };
// [supportCustomizedTrial: true]
const supportCustomizedTrial = (EXPERIMENT.multiPhase === true) ? false : true;
const disabledAddCustomizedTrial = ['DONE', 'ERROR', 'STOPPED'].includes(EXPERIMENT.status);
let showTitle = COLUMNPro; let showTitle = COLUMNPro;
const showColumn: Array<object> = []; const showColumn: Array<object> = [];
...@@ -361,6 +385,22 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -361,6 +385,22 @@ class TableList extends React.Component<TableListProps, TableListState> {
</Button> </Button>
</Popconfirm> </Popconfirm>
} }
{/* Add a new trial-customized trial */}
{
supportCustomizedTrial
?
<Button
type="primary"
className="common-style"
disabled={disabledAddCustomizedTrial}
onClick={this.setCustomizedTrial.bind(this, record.id)}
title="Customized trial"
>
<Icon type="copy" />
</Button>
:
null
}
</Row> </Row>
); );
}, },
...@@ -398,7 +438,7 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -398,7 +438,7 @@ class TableList extends React.Component<TableListProps, TableListState> {
expandedRowRender={this.openRow} expandedRowRender={this.openRow}
dataSource={tableSource} dataSource={tableSource}
className="commonTableStyle" className="commonTableStyle"
scroll={{x: 'max-content'}} scroll={{ x: 'max-content' }}
pagination={pageSize > 0 ? { pageSize } : false} pagination={pageSize > 0 ? { pageSize } : false}
/> />
{/* Intermediate Result Modal */} {/* Intermediate Result Modal */}
...@@ -458,7 +498,14 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -458,7 +498,14 @@ class TableList extends React.Component<TableListProps, TableListState> {
className="titleColumn" className="titleColumn"
/> />
</Modal> </Modal>
{/* compare trials based message */}
<Compare compareRows={selectRows} visible={isShowCompareModal} cancelFunc={this.hideCompareModal} /> <Compare compareRows={selectRows} visible={isShowCompareModal} cancelFunc={this.hideCompareModal} />
{/* clone trial parameters and could submit a customized trial */}
<Customize
visible={isShowCustomizedModal}
copyTrialId={copyTrialId}
closeCustomizeModal={this.closeCustomizedTrial}
/>
</Row> </Row>
); );
} }
......
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