App.tsx 10.4 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
51
    updateOverviewPage: () => {},
    // eslint-disable-next-line @typescript-eslint/no-empty-function
    changeExpandRowIDs: (_val: string, _type?: string): void => {}
52
53
});

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

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

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

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

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

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

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

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

Lijiaoa's avatar
Lijiaoa committed
136
    shouldComponentUpdate(nextProps: any, nextState: AppState): boolean {
137
        if (!(nextState.isUpdate || nextState.isUpdate === undefined)) {
Lijiaoa's avatar
Lijiaoa committed
138
139
140
141
142
143
            nextState.isUpdate = true;
            return false;
        }
        return true;
    }

144
    render(): React.ReactNode {
145
146
147
148
149
150
151
152
        const {
            interval,
            columnList,
            experimentUpdateBroadcast,
            trialsUpdateBroadcast,
            metricGraphMode,
            isillegalFinal,
            expWarningMessage,
153
            bestTrialEntries,
154
155
            maxDurationUnit,
            expandRowIDs
156
        } = this.state;
157
        if (experimentUpdateBroadcast === 0 || trialsUpdateBroadcast === 0) {
158
            return null; // TODO: render a loading page
159
        }
Lijiaoa's avatar
Lijiaoa committed
160
161
162
163
164
165
166
167
        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() }
        ];
168

169
        return (
170
171
172
173
174
175
            <React.Fragment>
                {isManagerExperimentPage() ? null : (
                    <Stack className='nni' style={{ minHeight: window.innerHeight }}>
                        <div className='header'>
                            <div className='headerCon'>
                                <NavCon changeInterval={this.changeInterval} refreshFunction={this.lastRefresh} />
176
                            </div>
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
                        </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,
209
210
211
                                        updateOverviewPage: this.updateOverviewPage,
                                        expandRowIDs,
                                        changeExpandRowIDs: this.changeExpandRowIDs
212
213
214
215
216
217
                                    }}
                                >
                                    {this.props.children}
                                </AppContext.Provider>
                            </Stack>
                        </Stack>
218
                    </Stack>
219
220
                )}
            </React.Fragment>
221
222
223
        );
    }

Lijiao's avatar
Lijiao committed
224
    private refresh = async (): Promise<void> => {
225
226
227
228
229
230
231
232
233
234
235
236
        // 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;
237
238
        }

Lijiaoa's avatar
Lijiaoa committed
239
        // experiment status and /trial-jobs api's status could decide website update
240
        if (['DONE', 'ERROR', 'STOPPED', 'VIEWED'].includes(EXPERIMENT.status) || TRIALS.jobListError()) {
241
            // experiment finished, refresh once more to ensure consistency
Lijiaoa's avatar
Lijiaoa committed
242
            this.setState(() => ({ interval: 0, isUpdate: false }));
243
            return;
244
        }
245

Lijiaoa's avatar
Lijiaoa committed
246
        this.timerId = window.setTimeout(this.refresh, this.state.interval * 1000);
247
    };
248

249
    public async lastRefresh(): Promise<void> {
250
251
        await EXPERIMENT.update();
        await TRIALS.update(true);
252
253
254
255
        this.setState(state => ({
            experimentUpdateBroadcast: state.experimentUpdateBroadcast + 1,
            trialsUpdateBroadcast: state.trialsUpdateBroadcast + 1
        }));
256
    }
Deshui Yu's avatar
Deshui Yu committed
257
258
259
}

export default App;