CustomizedTrial.tsx 11.6 KB
Newer Older
1
2
import * as React from 'react';
import axios from 'axios';
3
4
import { Stack, StackItem, PrimaryButton, DefaultButton } from '@fluentui/react';
import { Dialog, DialogType, DialogFooter } from '@fluentui/react/lib/Dialog';
5
6
import { MANAGER_IP } from '../../static/const';
import { EXPERIMENT, TRIALS } from '../../static/datamodel';
7
import { warining, errorBadge, completed } from '../buttons/Icon';
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import './customized.scss';

interface CustomizeProps {
    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
    changeMap: Map<string, string | number>; // store change key: value
}

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,
38
            changeMap: new Map()
39
40
41
42
43
44
45
        };
    }

    getFinalVal = (event: React.ChangeEvent<HTMLInputElement>): void => {
        const { name, value } = event.target;
        const { changeMap } = this.state;
        this.setState({ changeMap: changeMap.set(name, value) });
46
    };
47
48
49
50
51
52
53

    // [submit click] user add a new trial [submit a trial]
    addNewTrial = (): void => {
        const { searchSpace, copyTrialParameter, changeMap } = this.state;
        // get user edited hyperParameter, ps: will change data type if you modify the input val
        const customized = JSON.parse(JSON.stringify(copyTrialParameter));
        // changeMap: user changed keys: values
54
        changeMap.forEach(function(value, key) {
55
56
57
58
            customized[key] = value;
        });

        // true: parameters are wrong
59
        let parametersIllegal = false;
60
61
62
        Object.keys(customized).map(item => {
            if (item !== 'tag') {
                // unified data type
Lijiaoa's avatar
Lijiaoa committed
63
64
65
66
                if (
                    (typeof copyTrialParameter[item] === 'number' && typeof customized[item] === 'string') ||
                    (typeof copyTrialParameter[item] === 'boolean' && typeof customized[item] === 'string')
                ) {
67
68
                    customized[item] = JSON.parse(customized[item]);
                }
69
70
71
72
73
                if (searchSpace[item] === undefined) {
                    // sometimes the schema of trial parameters is different from search space
                    // e.g. Batch Tuner
                    return;
                }
74
                if (searchSpace[item]._type === 'choice') {
75
76
77
                    if (
                        searchSpace[item]._value.find((val: string | number) => val === customized[item]) === undefined
                    ) {
78
                        parametersIllegal = true;
79
80
81
                        return;
                    }
                } else {
82
83
84
85
                    if (
                        customized[item] < searchSpace[item]._value[0] ||
                        customized[item] > searchSpace[item]._value[1]
                    ) {
86
                        parametersIllegal = true;
87
88
89
90
91
                        return;
                    }
                }
            }
        });
92
        if (parametersIllegal !== false) {
93
94
95
96
97
98
            // open the warning modal
            this.setState(() => ({ isShowWarning: true, customParameters: customized }));
        } else {
            // submit a customized job
            this.submitCustomize(customized);
        }
99
    };
100
101
102
103
104

    warningConfirm = (): void => {
        this.setState(() => ({ isShowWarning: false }));
        const { customParameters } = this.state;
        this.submitCustomize(customParameters);
105
    };
106
107
108

    warningCancel = (): void => {
        this.setState(() => ({ isShowWarning: false }));
109
    };
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133

    submitCustomize = (customized: Record<string, any>): void => {
        // delete `tag` key
        for (const 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(() => {
                this.setState(() => ({ isShowSubmitFailed: true }));
            });
134
    };
135
136
137
138
139

    closeSucceedHint = (): void => {
        // also close customized trial modal
        this.setState(() => ({ isShowSubmitSucceed: false, changeMap: new Map() }));
        this.props.closeCustomizeModal();
140
    };
141
142
143
144
145

    closeFailedHint = (): void => {
        // also close customized trial modal
        this.setState(() => ({ isShowSubmitFailed: false, changeMap: new Map() }));
        this.props.closeCustomizeModal();
146
    };
147
148
149
150
151
152
153
154
155

    componentDidMount(): void {
        const { copyTrialId } = this.props;
        if (copyTrialId !== undefined && TRIALS.getTrial(copyTrialId) !== undefined) {
            const originCopyTrialPara = TRIALS.getTrial(copyTrialId).description.parameters;
            this.setState(() => ({ copyTrialParameter: originCopyTrialPara }));
        }
    }

156
157
158
159
160
161
162
    componentDidUpdate(prevProps: CustomizeProps): void {
        if (this.props.copyTrialId !== prevProps.copyTrialId) {
            const { copyTrialId } = this.props;
            if (copyTrialId !== undefined && TRIALS.getTrial(copyTrialId) !== undefined) {
                const originCopyTrialPara = TRIALS.getTrial(copyTrialId).description.parameters;
                this.setState(() => ({ copyTrialParameter: originCopyTrialPara }));
            }
163
164
        }
    }
165

166
167
168
    render(): React.ReactNode {
        const { closeCustomizeModal, visible } = this.props;
        const { isShowSubmitSucceed, isShowSubmitFailed, isShowWarning, customID, copyTrialParameter } = this.state;
169
170
171
        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?';
172
173
174
175
176
177
178
179
180
181
182
183
184
185
        return (
            <Stack>
                <Dialog
                    hidden={!visible} // required field!
                    dialogContentProps={{
                        type: DialogType.largeHeader,
                        title: 'Customized trial setting',
                        subText: 'You can submit a customized trial.'
                    }}
                    modalProps={{
                        isBlocking: false,
                        styles: { main: { maxWidth: 450 } }
                    }}
                >
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
                    <form className='hyper-box'>
                        {Object.keys(copyTrialParameter).map(item => (
                            <Stack horizontal key={item} className='hyper-form'>
                                <StackItem styles={{ root: { minWidth: 100 } }} className='title'>
                                    {item}
                                </StackItem>
                                <StackItem className='inputs'>
                                    <input
                                        type='text'
                                        name={item}
                                        defaultValue={copyTrialParameter[item]}
                                        onChange={this.getFinalVal}
                                    />
                                </StackItem>
                            </Stack>
                        ))}
202
203
204
205
206
207
208
209
210
                        {/* disable [tag] because we havn't support */}
                        {/* <Stack key="tag" horizontal className="hyper-form tag-input">
                            <StackItem grow={9} className="title">Tag</StackItem>
                            <StackItem grow={15} className="inputs">
                                <input type="text" value='Customized' />
                            </StackItem>
                        </Stack> */}
                    </form>
                    <DialogFooter>
211
212
                        <PrimaryButton text='Submit' onClick={this.addNewTrial} />
                        <DefaultButton text='Cancel' onClick={closeCustomizeModal} />
213
214
215
216
217
218
219
220
221
                    </DialogFooter>
                </Dialog>

                {/* clone: prompt succeed or failed */}
                <Dialog
                    hidden={!isShowSubmitSucceed}
                    onDismiss={this.closeSucceedHint}
                    dialogContentProps={{
                        type: DialogType.normal,
222
223
224
225
226
227
                        title: (
                            <div className='icon-color'>
                                {completed}
                                <b>Submit successfully</b>
                            </div>
                        ),
228
229
230
231
232
                        closeButtonAriaLabel: 'Close',
                        subText: `You can find your customized trial by Trial No.${customID}`
                    }}
                    modalProps={{
                        isBlocking: false,
233
                        styles: { main: { minWidth: 500 } }
234
235
236
                    }}
                >
                    <DialogFooter>
237
                        <PrimaryButton onClick={this.closeSucceedHint} text='OK' />
238
239
240
241
242
243
244
245
                    </DialogFooter>
                </Dialog>

                <Dialog
                    hidden={!isShowSubmitFailed}
                    onDismiss={this.closeSucceedHint}
                    dialogContentProps={{
                        type: DialogType.normal,
246
                        title: <div className='icon-error'>{errorBadge}Submit Failed</div>,
247
248
249
250
251
                        closeButtonAriaLabel: 'Close',
                        subText: 'Unknown error.'
                    }}
                    modalProps={{
                        isBlocking: false,
252
                        styles: { main: { minWidth: 500 } }
253
254
255
                    }}
                >
                    <DialogFooter>
256
                        <PrimaryButton onClick={this.closeFailedHint} text='OK' />
257
258
259
260
261
262
263
264
265
                    </DialogFooter>
                </Dialog>

                {/* hyperParameter not match search space, warning modal */}
                <Dialog
                    hidden={!isShowWarning}
                    onDismiss={this.closeSucceedHint}
                    dialogContentProps={{
                        type: DialogType.normal,
266
                        title: <div className='icon-error'>{warining}Warning</div>,
267
268
269
270
271
                        closeButtonAriaLabel: 'Close',
                        subText: `${warning}`
                    }}
                    modalProps={{
                        isBlocking: false,
272
                        styles: { main: { minWidth: 500 } }
273
274
275
                    }}
                >
                    <DialogFooter>
276
277
                        <PrimaryButton onClick={this.warningConfirm} text='Confirm' />
                        <DefaultButton onClick={this.warningCancel} text='Cancel' />
278
279
280
281
282
283
284
                    </DialogFooter>
                </Dialog>
            </Stack>
        );
    }
}

285
export default Customize;