App.tsx 10.7 KB
Newer Older
Deshui Yu's avatar
Deshui Yu committed
1
import * as React from 'react';
2
import { Stack } from '@fluentui/react';
3
4
import { COLUMN } from './static/const';
import { EXPERIMENT, TRIALS } from './static/datamodel';
5
import { isManagerExperimentPage } from './static/function';
6
import NavCon from './components/NavCon';
7
import MessageInfo from './components/modals/MessageInfo';
8
import { SlideNavBtns } from './components/slideNav/SlideNavBtns';
9
10
11
12
const echarts = require('echarts/lib/echarts');
echarts.registerTheme('nni_theme', {
    color: '#3c8dbc'
});
13
import './App.scss';
14
import './static/style/common.scss';
15
import './static/style/trialsDetail.scss';
Deshui Yu's avatar
Deshui Yu committed
16

17
interface AppState {
18
    interval: number;
19
    columnList: string[];
20
21
    experimentUpdateBroadcast: number;
    trialsUpdateBroadcast: number;
22
    maxDurationUnit: string;
Lijiao's avatar
Lijiao committed
23
    metricGraphMode: 'max' | 'min'; // tuner's optimize_mode filed
24
25
    isillegalFinal: boolean;
    expWarningMessage: string;
Lijiaoa's avatar
Lijiaoa committed
26
    bestTrialEntries: string; // for overview page: best trial entreis
Lijiaoa's avatar
Lijiaoa committed
27
    isUpdate: boolean;
28
    expandRowIDs: Set<string>;
29
30
}

31
32
33
34
35
36
37
export const AppContext = React.createContext({
    interval: 10, // sendons
    columnList: COLUMN,
    experimentUpdateBroadcast: 0,
    trialsUpdateBroadcast: 0,
    metricGraphMode: 'max',
    bestTrialEntries: '10',
38
    maxDurationUnit: 'm',
39
    expandRowIDs: new Set(['']),
40
41
42
43
44
45
46
47
48
    // eslint-disable-next-line @typescript-eslint/no-empty-function
    changeColumn: (_val: string[]): void => {},
    // eslint-disable-next-line @typescript-eslint/no-empty-function
    changeMetricGraphMode: (_val: 'max' | 'min'): void => {},
    // eslint-disable-next-line @typescript-eslint/no-empty-function
    changeMaxDurationUnit: (_val: string): void => {},
    // eslint-disable-next-line @typescript-eslint/no-empty-function
    changeEntries: (_val: string): void => {},
    // eslint-disable-next-line @typescript-eslint/no-empty-function
49
50
    updateOverviewPage: () => {},
    // eslint-disable-next-line @typescript-eslint/no-empty-function
51
52
    updateDetailPage: () => {},
    // eslint-disable-next-line @typescript-eslint/no-empty-function
53
    changeExpandRowIDs: (_val: string, _type?: string): void => {}
54
55
});

56
class App extends React.Component<{}, AppState> {
57
58
    private timerId!: number | undefined;
    private firstLoad: boolean = false; // when click refresh selector options
59
60
61
62
63
64
65
    constructor(props: {}) {
        super(props);
        this.state = {
            interval: 10, // sendons
            columnList: COLUMN,
            experimentUpdateBroadcast: 0,
            trialsUpdateBroadcast: 0,
66
            metricGraphMode: 'max',
67
            maxDurationUnit: 'm',
68
            isillegalFinal: false,
Lijiaoa's avatar
Lijiaoa committed
69
            expWarningMessage: '',
Lijiaoa's avatar
Lijiaoa committed
70
            bestTrialEntries: '10',
71
72
            isUpdate: true,
            expandRowIDs: new Set()
73
        };
74
75
    }

Lijiao's avatar
Lijiao committed
76
    async componentDidMount(): Promise<void> {
77
        await Promise.all([EXPERIMENT.init(), TRIALS.init()]);
78
79
        this.setState(state => ({
            experimentUpdateBroadcast: state.experimentUpdateBroadcast + 1,
Lijiaoa's avatar
Lijiaoa committed
80
            trialsUpdateBroadcast: state.trialsUpdateBroadcast + 1,
81
            metricGraphMode: EXPERIMENT.optimizeMode === 'minimize' ? 'min' : 'max'
Lijiaoa's avatar
Lijiaoa committed
82
83
        }));
        this.timerId = window.setTimeout(this.refresh, this.state.interval * 100);
84
85
    }

Lijiao's avatar
Lijiao committed
86
    changeInterval = (interval: number): void => {
87
88
        window.clearTimeout(this.timerId);
        if (interval === 0) {
Lijiaoa's avatar
Lijiaoa committed
89
            return;
90
        }
91
92
        // setState will trigger page refresh at once.
        // setState is asyc, interval not update to (this.state.interval) at once.
Lijiaoa's avatar
Lijiaoa committed
93
        this.setState({ interval }, () => {
94
95
96
            this.firstLoad = true;
            this.refresh();
        });
97
    };
98

99
    // TODO: use local storage
100
    changeColumn = (columnList: string[]): void => {
101
        this.setState({ columnList: columnList });
102
    };
103

104
105
106
107
108
109
110
111
112
113
114
115
116
117
    changeExpandRowIDs = (id: string, type?: string): void => {
        const currentExpandRowIDs = this.state.expandRowIDs;

        if (!currentExpandRowIDs.has(id)) {
            currentExpandRowIDs.add(id);
        } else {
            if (!(type !== undefined && type === 'chart')) {
                currentExpandRowIDs.delete(id);
            }
        }

        this.setState({ expandRowIDs: currentExpandRowIDs });
    };

Lijiao's avatar
Lijiao committed
118
    changeMetricGraphMode = (val: 'max' | 'min'): void => {
Lijiao's avatar
Lijiao committed
119
        this.setState({ metricGraphMode: val });
120
    };
Lijiao's avatar
Lijiao committed
121

Lijiaoa's avatar
Lijiaoa committed
122
123
    // overview best trial module
    changeEntries = (entries: string): void => {
Lijiaoa's avatar
Lijiaoa committed
124
        this.setState({ bestTrialEntries: entries });
125
    };
Lijiaoa's avatar
Lijiaoa committed
126

127
128
129
130
131
    // overview max duration unit
    changeMaxDurationUnit = (unit: string): void => {
        this.setState({ maxDurationUnit: unit });
    };

132
133
134
135
136
137
    updateOverviewPage = (): void => {
        this.setState(state => ({
            experimentUpdateBroadcast: state.experimentUpdateBroadcast + 1
        }));
    };

138
139
140
141
142
143
    updateDetailPage = (): void => {
        this.setState(state => ({
            trialsUpdateBroadcast: state.trialsUpdateBroadcast + 1
        }));
    };

Lijiaoa's avatar
Lijiaoa committed
144
    shouldComponentUpdate(nextProps: any, nextState: AppState): boolean {
145
        if (!(nextState.isUpdate || nextState.isUpdate === undefined)) {
Lijiaoa's avatar
Lijiaoa committed
146
147
148
149
150
151
            nextState.isUpdate = true;
            return false;
        }
        return true;
    }

152
    render(): React.ReactNode {
153
154
155
156
157
158
159
160
        const {
            interval,
            columnList,
            experimentUpdateBroadcast,
            trialsUpdateBroadcast,
            metricGraphMode,
            isillegalFinal,
            expWarningMessage,
161
            bestTrialEntries,
162
163
            maxDurationUnit,
            expandRowIDs
164
        } = this.state;
165
        if (experimentUpdateBroadcast === 0 || trialsUpdateBroadcast === 0) {
166
            return null; // TODO: render a loading page
167
        }
Lijiaoa's avatar
Lijiaoa committed
168
169
170
171
172
173
174
175
        const errorList = [
            { errorWhere: TRIALS.jobListError(), errorMessage: TRIALS.getJobErrorMessage() },
            { errorWhere: EXPERIMENT.experimentError(), errorMessage: EXPERIMENT.getExperimentMessage() },
            { errorWhere: EXPERIMENT.statusError(), errorMessage: EXPERIMENT.getStatusMessage() },
            { errorWhere: TRIALS.MetricDataError(), errorMessage: TRIALS.getMetricDataErrorMessage() },
            { errorWhere: TRIALS.latestMetricDataError(), errorMessage: TRIALS.getLatestMetricDataErrorMessage() },
            { errorWhere: TRIALS.metricDataRangeError(), errorMessage: TRIALS.metricDataRangeErrorMessage() }
        ];
176

177
        return (
178
179
180
181
182
183
            <React.Fragment>
                {isManagerExperimentPage() ? null : (
                    <Stack className='nni' style={{ minHeight: window.innerHeight }}>
                        <div className='header'>
                            <div className='headerCon'>
                                <NavCon changeInterval={this.changeInterval} refreshFunction={this.lastRefresh} />
184
                            </div>
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
                        </div>
                        <Stack className='contentBox'>
                            <Stack className='content'>
                                {/* search space & config */}
                                <SlideNavBtns />
                                {/* if api has error field, show error message */}
                                {errorList.map(
                                    (item, key) =>
                                        item.errorWhere && (
                                            <div key={key} className='warning'>
                                                <MessageInfo info={item.errorMessage} typeInfo='error' />
                                            </div>
                                        )
                                )}
                                {isillegalFinal && (
                                    <div className='warning'>
                                        <MessageInfo info={expWarningMessage} typeInfo='warning' />
                                    </div>
                                )}
                                <AppContext.Provider
                                    value={{
                                        interval,
                                        columnList,
                                        changeColumn: this.changeColumn,
                                        experimentUpdateBroadcast,
                                        trialsUpdateBroadcast,
                                        metricGraphMode,
                                        maxDurationUnit,
                                        changeMaxDurationUnit: this.changeMaxDurationUnit,
                                        changeMetricGraphMode: this.changeMetricGraphMode,
                                        bestTrialEntries,
                                        changeEntries: this.changeEntries,
217
                                        updateOverviewPage: this.updateOverviewPage,
218
                                        updateDetailPage: this.updateDetailPage,
219
220
                                        expandRowIDs,
                                        changeExpandRowIDs: this.changeExpandRowIDs
221
222
223
224
225
226
                                    }}
                                >
                                    {this.props.children}
                                </AppContext.Provider>
                            </Stack>
                        </Stack>
227
                    </Stack>
228
229
                )}
            </React.Fragment>
230
231
232
        );
    }

Lijiao's avatar
Lijiao committed
233
    private refresh = async (): Promise<void> => {
234
235
236
237
238
239
240
241
242
243
244
245
        // resolve this question: 10s -> 20s, page refresh twice.
        // only refresh this page after clicking the refresh options
        if (this.firstLoad !== true) {
            const [experimentUpdated, trialsUpdated] = await Promise.all([EXPERIMENT.update(), TRIALS.update()]);
            if (experimentUpdated) {
                this.setState(state => ({ experimentUpdateBroadcast: state.experimentUpdateBroadcast + 1 }));
            }
            if (trialsUpdated) {
                this.setState(state => ({ trialsUpdateBroadcast: state.trialsUpdateBroadcast + 1 }));
            }
        } else {
            this.firstLoad = false;
246
247
        }

Lijiaoa's avatar
Lijiaoa committed
248
        // experiment status and /trial-jobs api's status could decide website update
249
        if (['DONE', 'ERROR', 'STOPPED', 'VIEWED'].includes(EXPERIMENT.status) || TRIALS.jobListError()) {
250
            // experiment finished, refresh once more to ensure consistency
Lijiaoa's avatar
Lijiaoa committed
251
            this.setState(() => ({ interval: 0, isUpdate: false }));
252
            return;
253
        }
254

Lijiaoa's avatar
Lijiaoa committed
255
        this.timerId = window.setTimeout(this.refresh, this.state.interval * 1000);
256
    };
257

258
    public async lastRefresh(): Promise<void> {
259
260
        await EXPERIMENT.update();
        await TRIALS.update(true);
261
262
263
264
        this.setState(state => ({
            experimentUpdateBroadcast: state.experimentUpdateBroadcast + 1,
            trialsUpdateBroadcast: state.trialsUpdateBroadcast + 1
        }));
265
    }
Deshui Yu's avatar
Deshui Yu committed
266
267
268
}

export default App;