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';
Lijiaoa's avatar
Lijiaoa committed
3
4
5
6
7
8
9
10
11
12
13
import { SlideNavBtns } from '@components/nav/slideNav/SlideNavBtns';
import { EXPERIMENT, TRIALS } from '@static/datamodel';
import NavCon from '@components/nav/Nav';
import MessageInfo from '@components/common/MessageInfo';
import { COLUMN } from '@static/const';
import { isManagerExperimentPage } from '@static/function';

import '@style/App.scss';
import '@style/common/common.scss';
import '@style/experiment/trialdetail/trialsDetail.scss';

14
15
16
17
const echarts = require('echarts/lib/echarts');
echarts.registerTheme('nni_theme', {
    color: '#3c8dbc'
});
Deshui Yu's avatar
Deshui Yu committed
18

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

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

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

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

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

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

106
107
108
109
110
111
112
113
114
115
116
117
118
119
    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
120
    changeMetricGraphMode = (val: 'max' | 'min'): void => {
Lijiao's avatar
Lijiao committed
121
        this.setState({ metricGraphMode: val });
122
    };
Lijiao's avatar
Lijiao committed
123

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

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

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

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

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

154
    render(): React.ReactNode {
155
156
157
158
159
160
161
162
        const {
            interval,
            columnList,
            experimentUpdateBroadcast,
            trialsUpdateBroadcast,
            metricGraphMode,
            isillegalFinal,
            expWarningMessage,
163
            bestTrialEntries,
164
165
            maxDurationUnit,
            expandRowIDs
166
        } = this.state;
167
        if (experimentUpdateBroadcast === 0 || trialsUpdateBroadcast === 0) {
168
            return null; // TODO: render a loading page
169
        }
Lijiaoa's avatar
Lijiaoa committed
170
171
172
173
174
175
176
177
        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() }
        ];
178

179
        return (
180
181
182
183
184
185
            <React.Fragment>
                {isManagerExperimentPage() ? null : (
                    <Stack className='nni' style={{ minHeight: window.innerHeight }}>
                        <div className='header'>
                            <div className='headerCon'>
                                <NavCon changeInterval={this.changeInterval} refreshFunction={this.lastRefresh} />
186
                            </div>
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
217
218
                        </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,
219
                                        updateOverviewPage: this.updateOverviewPage,
220
                                        updateDetailPage: this.updateDetailPage,
221
222
                                        expandRowIDs,
                                        changeExpandRowIDs: this.changeExpandRowIDs
223
224
225
226
227
228
                                    }}
                                >
                                    {this.props.children}
                                </AppContext.Provider>
                            </Stack>
                        </Stack>
229
                    </Stack>
230
231
                )}
            </React.Fragment>
232
233
234
        );
    }

Lijiao's avatar
Lijiao committed
235
    private refresh = async (): Promise<void> => {
236
237
238
239
240
241
242
243
244
245
246
247
        // 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;
248
249
        }

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

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

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

export default App;