"vscode:/vscode.git/clone" did not exist on "568b73fdf86dcc12a88b526633c7bef4bf8cf58f"
Unverified Commit 430bfea5 authored by liuzhe-lz's avatar liuzhe-lz Committed by GitHub
Browse files

Implement feature: customized trials

parent b3847186
...@@ -105,7 +105,7 @@ abstract class Manager { ...@@ -105,7 +105,7 @@ abstract class Manager {
public abstract importData(data: string): Promise<void>; public abstract importData(data: string): Promise<void>;
public abstract exportData(): Promise<string>; public abstract exportData(): Promise<string>;
public abstract addCustomizedTrialJob(hyperParams: string): Promise<void>; public abstract addCustomizedTrialJob(hyperParams: string): Promise<number>;
public abstract cancelTrialJobByUser(trialJobId: string): Promise<void>; public abstract cancelTrialJobByUser(trialJobId: string): Promise<void>;
public abstract listTrialJobs(status?: TrialJobStatus): Promise<TrialJobInfo[]>; public abstract listTrialJobs(status?: TrialJobStatus): Promise<TrialJobInfo[]>;
......
...@@ -58,11 +58,6 @@ interface TrialJobDetail { ...@@ -58,11 +58,6 @@ interface TrialJobDetail {
isEarlyStopped?: boolean; isEarlyStopped?: boolean;
} }
interface HostJobDetail {
readonly id: string;
readonly status: string;
}
/** /**
* define TrialJobMetric * define TrialJobMetric
*/ */
......
...@@ -50,13 +50,12 @@ class NNIManager implements Manager { ...@@ -50,13 +50,12 @@ class NNIManager implements Manager {
private dispatcher: IpcInterface | undefined; private dispatcher: IpcInterface | undefined;
private currSubmittedTrialNum: number; // need to be recovered private currSubmittedTrialNum: number; // need to be recovered
private trialConcurrencyChange: number; // >0: increase, <0: decrease private trialConcurrencyChange: number; // >0: increase, <0: decrease
private customizedTrials: string[]; // need to be recovered
private log: Logger; private log: Logger;
private dataStore: DataStore; private dataStore: DataStore;
private experimentProfile: ExperimentProfile; private experimentProfile: ExperimentProfile;
private dispatcherPid: number; private dispatcherPid: number;
private status: NNIManagerStatus; private status: NNIManagerStatus;
private waitingTrials: string[]; private waitingTrials: TrialJobApplicationForm[];
private trialJobs: Map<string, TrialJobDetail>; private trialJobs: Map<string, TrialJobDetail>;
private trialDataForTuner: string; private trialDataForTuner: string;
private readonly: boolean; private readonly: boolean;
...@@ -66,7 +65,6 @@ class NNIManager implements Manager { ...@@ -66,7 +65,6 @@ class NNIManager implements Manager {
constructor() { constructor() {
this.currSubmittedTrialNum = 0; this.currSubmittedTrialNum = 0;
this.trialConcurrencyChange = 0; this.trialConcurrencyChange = 0;
this.customizedTrials = [];
this.trainingService = component.get(TrainingService); this.trainingService = component.get(TrainingService);
assert(this.trainingService); assert(this.trainingService);
this.dispatcherPid = 0; this.dispatcherPid = 0;
...@@ -131,19 +129,34 @@ class NNIManager implements Manager { ...@@ -131,19 +129,34 @@ class NNIManager implements Manager {
return this.dataStore.exportTrialHpConfigs(); return this.dataStore.exportTrialHpConfigs();
} }
public addCustomizedTrialJob(hyperParams: string): Promise<void> { public addCustomizedTrialJob(hyperParams: string): Promise<number> {
if (this.readonly) { if (this.readonly) {
return Promise.reject(new Error('Error: can not add customized trial job in readonly mode!')); return Promise.reject(new Error('Error: can not add customized trial job in readonly mode!'));
} }
if (this.currSubmittedTrialNum >= this.experimentProfile.params.maxTrialNum) { if (this.currSubmittedTrialNum >= this.experimentProfile.params.maxTrialNum) {
return Promise.reject( return Promise.reject(new Error('reach maxTrialNum'));
new Error('reach maxTrialNum')
);
} }
this.customizedTrials.push(hyperParams);
// TODO: NNI manager should not peek tuner's internal protocol, let's refactor this later
const packedParameter = {
parameter_id: null,
parameter_source: 'customized',
parameters: JSON.parse(hyperParams)
}
const form: TrialJobApplicationForm = {
sequenceId: this.experimentProfile.nextSequenceId++,
hyperParameters: {
value: JSON.stringify(packedParameter),
index: 0
}
};
this.waitingTrials.push(form);
// trial id has not been generated yet, thus use '' instead // trial id has not been generated yet, thus use '' instead
return this.dataStore.storeTrialJobEvent('ADD_CUSTOMIZED', '', hyperParams); this.dataStore.storeTrialJobEvent('ADD_CUSTOMIZED', '', hyperParams);
return Promise.resolve(form.sequenceId);
} }
public async cancelTrialJobByUser(trialJobId: string): Promise<void> { public async cancelTrialJobByUser(trialJobId: string): Promise<void> {
...@@ -560,18 +573,7 @@ class NNIManager implements Manager { ...@@ -560,18 +573,7 @@ class NNIManager implements Manager {
this.trialConcurrencyChange = requestTrialNum; this.trialConcurrencyChange = requestTrialNum;
} }
const requestCustomTrialNum: number = Math.min(requestTrialNum, this.customizedTrials.length); this.requestTrialJobs(requestTrialNum);
for (let i: number = 0; i < requestCustomTrialNum; i++) {
// ask tuner for more trials
if (this.customizedTrials.length > 0) {
const hyperParams: string | undefined = this.customizedTrials.shift();
this.dispatcher.sendCommand(ADD_CUSTOMIZED_TRIAL_JOB, hyperParams);
}
}
if (requestTrialNum - requestCustomTrialNum > 0) {
this.requestTrialJobs(requestTrialNum - requestCustomTrialNum);
}
// check maxtrialnum and maxduration here // check maxtrialnum and maxduration here
// NO_MORE_TRIAL is more like a subset of RUNNING, because during RUNNING tuner // NO_MORE_TRIAL is more like a subset of RUNNING, because during RUNNING tuner
...@@ -609,26 +611,16 @@ class NNIManager implements Manager { ...@@ -609,26 +611,16 @@ class NNIManager implements Manager {
this.currSubmittedTrialNum >= this.experimentProfile.params.maxTrialNum) { this.currSubmittedTrialNum >= this.experimentProfile.params.maxTrialNum) {
break; break;
} }
const hyperParams: string | undefined = this.waitingTrials.shift(); const form = this.waitingTrials.shift() as TrialJobApplicationForm;
if (hyperParams === undefined) {
throw new Error(`Error: invalid hyper-parameters for job submission: ${hyperParams}`);
}
this.currSubmittedTrialNum++; this.currSubmittedTrialNum++;
const trialJobAppForm: TrialJobApplicationForm = { this.log.info(`submitTrialJob: form: ${JSON.stringify(form)}`);
sequenceId: this.experimentProfile.nextSequenceId++, const trialJobDetail: TrialJobDetail = await this.trainingService.submitTrialJob(form);
hyperParameters: {
value: hyperParams,
index: 0
}
};
this.log.info(`submitTrialJob: form: ${JSON.stringify(trialJobAppForm)}`);
const trialJobDetail: TrialJobDetail = await this.trainingService.submitTrialJob(trialJobAppForm);
await this.storeExperimentProfile(); await this.storeExperimentProfile();
this.trialJobs.set(trialJobDetail.id, Object.assign({}, trialJobDetail)); this.trialJobs.set(trialJobDetail.id, Object.assign({}, trialJobDetail));
const trialJobDetailSnapshot: TrialJobDetail | undefined = this.trialJobs.get(trialJobDetail.id); const trialJobDetailSnapshot: TrialJobDetail | undefined = this.trialJobs.get(trialJobDetail.id);
if (trialJobDetailSnapshot != undefined) { if (trialJobDetailSnapshot != undefined) {
await this.dataStore.storeTrialJobEvent( await this.dataStore.storeTrialJobEvent(
trialJobDetailSnapshot.status, trialJobDetailSnapshot.id, hyperParams, trialJobDetailSnapshot); trialJobDetailSnapshot.status, trialJobDetailSnapshot.id, form.hyperParameters.value, trialJobDetailSnapshot);
} else { } else {
assert(false, `undefined trialJobDetail in trialJobs: ${trialJobDetail.id}`); assert(false, `undefined trialJobDetail in trialJobs: ${trialJobDetail.id}`);
} }
...@@ -734,7 +726,14 @@ class NNIManager implements Manager { ...@@ -734,7 +726,14 @@ class NNIManager implements Manager {
this.log.warning('It is not supposed to receive more trials after NO_MORE_TRIAL is set'); this.log.warning('It is not supposed to receive more trials after NO_MORE_TRIAL is set');
this.setStatus('RUNNING'); this.setStatus('RUNNING');
} }
this.waitingTrials.push(content); const form: TrialJobApplicationForm = {
sequenceId: this.experimentProfile.nextSequenceId++,
hyperParameters: {
value: content,
index: 0
}
};
this.waitingTrials.push(form);
break; break;
case SEND_TRIAL_JOB_PARAMETER: case SEND_TRIAL_JOB_PARAMETER:
const tunerCommand: any = JSON.parse(content); const tunerCommand: any = JSON.parse(content);
......
...@@ -121,7 +121,7 @@ describe('Unit test for nnimanager', function () { ...@@ -121,7 +121,7 @@ describe('Unit test for nnimanager', function () {
it('test addCustomizedTrialJob', () => { it('test addCustomizedTrialJob', () => {
return nniManager.addCustomizedTrialJob('hyperParams').then(() => { return nniManager.addCustomizedTrialJob('"hyperParams"').then(() => {
}).catch((error) => { }).catch((error) => {
assert.fail(error); assert.fail(error);
...@@ -273,7 +273,7 @@ describe('Unit test for nnimanager', function () { ...@@ -273,7 +273,7 @@ describe('Unit test for nnimanager', function () {
it('test addCustomizedTrialJob reach maxTrialNum', () => { it('test addCustomizedTrialJob reach maxTrialNum', () => {
// test currSubmittedTrialNum reach maxTrialNum // test currSubmittedTrialNum reach maxTrialNum
return nniManager.addCustomizedTrialJob('hyperParam').then(() => { return nniManager.addCustomizedTrialJob('"hyperParam"').then(() => {
nniManager.getTrialJobStatistics().then(function (trialJobStatistics) { nniManager.getTrialJobStatistics().then(function (trialJobStatistics) {
if (trialJobStatistics[0].trialJobStatus === 'WAITING') if (trialJobStatistics[0].trialJobStatus === 'WAITING')
expect(trialJobStatistics[0].trialJobNumber).to.be.equal(2); expect(trialJobStatistics[0].trialJobNumber).to.be.equal(2);
......
...@@ -236,8 +236,8 @@ class NNIRestHandler { ...@@ -236,8 +236,8 @@ class NNIRestHandler {
private addTrialJob(router: Router): void { private addTrialJob(router: Router): void {
router.post('/trial-jobs', async (req: Request, res: Response) => { router.post('/trial-jobs', async (req: Request, res: Response) => {
this.nniManager.addCustomizedTrialJob(JSON.stringify(req.body)).then(() => { this.nniManager.addCustomizedTrialJob(JSON.stringify(req.body)).then((sequenceId: number) => {
res.send(); res.send({sequenceId});
}).catch((err: Error) => { }).catch((err: Error) => {
this.handle_error(err, res); this.handle_error(err, res);
}); });
......
...@@ -65,8 +65,8 @@ export class MockedNNIManager extends Manager { ...@@ -65,8 +65,8 @@ export class MockedNNIManager extends Manager {
return deferred.promise; return deferred.promise;
} }
public addCustomizedTrialJob(hyperParams: string): Promise<void> { public addCustomizedTrialJob(hyperParams: string): Promise<number> {
return Promise.resolve(); return Promise.resolve(99);
} }
public resumeExperiment(): Promise<void> { public resumeExperiment(): Promise<void> {
......
...@@ -136,7 +136,6 @@ class MsgDispatcher(MsgDispatcherBase): ...@@ -136,7 +136,6 @@ class MsgDispatcher(MsgDispatcherBase):
# data: parameters # data: parameters
id_ = _create_parameter_id() id_ = _create_parameter_id()
_customized_parameter_ids.add(id_) _customized_parameter_ids.add(id_)
send(CommandType.NewTrialJob, _pack_parameter(id_, data, customized=True))
def handle_report_metric_data(self, data): def handle_report_metric_data(self, data):
""" """
...@@ -185,7 +184,7 @@ class MsgDispatcher(MsgDispatcherBase): ...@@ -185,7 +184,7 @@ class MsgDispatcher(MsgDispatcherBase):
""" """
id_ = data['parameter_id'] id_ = data['parameter_id']
value = data['value'] value = data['value']
if id_ in _customized_parameter_ids: if not id_ or id_ in _customized_parameter_ids:
if not hasattr(self.tuner, '_accept_customized'): if not hasattr(self.tuner, '_accept_customized'):
self.tuner._accept_customized = False self.tuner._accept_customized = False
if not self.tuner._accept_customized: if not self.tuner._accept_customized:
...@@ -194,8 +193,8 @@ class MsgDispatcher(MsgDispatcherBase): ...@@ -194,8 +193,8 @@ class MsgDispatcher(MsgDispatcherBase):
customized = True customized = True
else: else:
customized = False customized = False
self.tuner.receive_trial_result(id_, _trial_params[id_], value, customized=customized, self.tuner.receive_trial_result(id_, _trial_params[id_], value, customized=customized,
trial_job_id=data.get('trial_job_id')) trial_job_id=data.get('trial_job_id'))
def _handle_intermediate_metric_data(self, data): def _handle_intermediate_metric_data(self, data):
"""Call assessor to process intermediate results """Call assessor to process intermediate results
......
...@@ -41,7 +41,7 @@ logging.basicConfig(level=logging.INFO) ...@@ -41,7 +41,7 @@ logging.basicConfig(level=logging.INFO)
logger = logging.getLogger('test_tuner') logger = logging.getLogger('test_tuner')
class TunerTestCase(TestCase): class BuiltinTunersTestCase(TestCase):
""" """
Targeted at testing functions of built-in tuners, including Targeted at testing functions of built-in tuners, including
- [ ] load_checkpoint - [ ] load_checkpoint
......
...@@ -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