CustomizedTrial.tsx 11.4 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
63
64
65
        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]);
                }
66
67
68
69
70
                if (searchSpace[item] === undefined) {
                    // sometimes the schema of trial parameters is different from search space
                    // e.g. Batch Tuner
                    return;
                }
71
                if (searchSpace[item]._type === 'choice') {
72
73
74
                    if (
                        searchSpace[item]._value.find((val: string | number) => val === customized[item]) === undefined
                    ) {
75
                        parametersIllegal = true;
76
77
78
                        return;
                    }
                } else {
79
80
81
82
                    if (
                        customized[item] < searchSpace[item]._value[0] ||
                        customized[item] > searchSpace[item]._value[1]
                    ) {
83
                        parametersIllegal = true;
84
85
86
87
88
                        return;
                    }
                }
            }
        });
89
        if (parametersIllegal !== false) {
90
91
92
93
94
95
            // open the warning modal
            this.setState(() => ({ isShowWarning: true, customParameters: customized }));
        } else {
            // submit a customized job
            this.submitCustomize(customized);
        }
96
    };
97
98
99
100
101

    warningConfirm = (): void => {
        this.setState(() => ({ isShowWarning: false }));
        const { customParameters } = this.state;
        this.submitCustomize(customParameters);
102
    };
103
104
105

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

    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 }));
            });
131
    };
132
133
134
135
136

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

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

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

153
154
155
156
157
158
159
    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 }));
            }
160
161
        }
    }
162

163
164
165
    render(): React.ReactNode {
        const { closeCustomizeModal, visible } = this.props;
        const { isShowSubmitSucceed, isShowSubmitFailed, isShowWarning, customID, copyTrialParameter } = this.state;
166
167
168
        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?';
169
170
171
172
173
174
175
176
177
178
179
180
181
182
        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 } }
                    }}
                >
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
                    <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>
                        ))}
199
200
201
202
203
204
205
206
207
                        {/* 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>
208
209
                        <PrimaryButton text='Submit' onClick={this.addNewTrial} />
                        <DefaultButton text='Cancel' onClick={closeCustomizeModal} />
210
211
212
213
214
215
216
217
218
                    </DialogFooter>
                </Dialog>

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

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

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

282
export default Customize;