App.tsx 9.42 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
import './App.scss';
10
import './static/style/common.scss';
Deshui Yu's avatar
Deshui Yu committed
11

12
interface AppState {
13
    interval: number;
14
    columnList: string[];
15
16
    experimentUpdateBroadcast: number;
    trialsUpdateBroadcast: number;
17
    maxDurationUnit: string;
Lijiao's avatar
Lijiao committed
18
    metricGraphMode: 'max' | 'min'; // tuner's optimize_mode filed
19
20
    isillegalFinal: boolean;
    expWarningMessage: string;
Lijiaoa's avatar
Lijiaoa committed
21
    bestTrialEntries: string; // for overview page: best trial entreis
Lijiaoa's avatar
Lijiaoa committed
22
    isUpdate: boolean;
23
24
}

25
26
27
28
29
30
31
export const AppContext = React.createContext({
    interval: 10, // sendons
    columnList: COLUMN,
    experimentUpdateBroadcast: 0,
    trialsUpdateBroadcast: 0,
    metricGraphMode: 'max',
    bestTrialEntries: '10',
32
    maxDurationUnit: 'm',
33
34
35
36
37
38
39
40
41
    // 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
42
    updateOverviewPage: () => {}
43
44
});

45
class App extends React.Component<{}, AppState> {
46
47
    private timerId!: number | undefined;
    private firstLoad: boolean = false; // when click refresh selector options
48
49
50
51
52
53
54
    constructor(props: {}) {
        super(props);
        this.state = {
            interval: 10, // sendons
            columnList: COLUMN,
            experimentUpdateBroadcast: 0,
            trialsUpdateBroadcast: 0,
55
            metricGraphMode: 'max',
56
            maxDurationUnit: 'm',
57
            isillegalFinal: false,
Lijiaoa's avatar
Lijiaoa committed
58
            expWarningMessage: '',
Lijiaoa's avatar
Lijiaoa committed
59
60
            bestTrialEntries: '10',
            isUpdate: true
61
        };
62
63
    }

Lijiao's avatar
Lijiao committed
64
    async componentDidMount(): Promise<void> {
65
        await Promise.all([EXPERIMENT.init(), TRIALS.init()]);
66
67
        this.setState(state => ({
            experimentUpdateBroadcast: state.experimentUpdateBroadcast + 1,
Lijiaoa's avatar
Lijiaoa committed
68
            trialsUpdateBroadcast: state.trialsUpdateBroadcast + 1,
69
            metricGraphMode: EXPERIMENT.optimizeMode === 'minimize' ? 'min' : 'max'
Lijiaoa's avatar
Lijiaoa committed
70
71
        }));
        this.timerId = window.setTimeout(this.refresh, this.state.interval * 100);
72
73
    }

Lijiao's avatar
Lijiao committed
74
    changeInterval = (interval: number): void => {
75
76
        window.clearTimeout(this.timerId);
        if (interval === 0) {
Lijiaoa's avatar
Lijiaoa committed
77
            return;
78
        }
79
80
        // setState will trigger page refresh at once.
        // setState is asyc, interval not update to (this.state.interval) at once.
Lijiaoa's avatar
Lijiaoa committed
81
        this.setState({ interval }, () => {
82
83
84
            this.firstLoad = true;
            this.refresh();
        });
85
    };
86

87
    // TODO: use local storage
88
    changeColumn = (columnList: string[]): void => {
89
        this.setState({ columnList: columnList });
90
    };
91

Lijiao's avatar
Lijiao committed
92
    changeMetricGraphMode = (val: 'max' | 'min'): void => {
Lijiao's avatar
Lijiao committed
93
        this.setState({ metricGraphMode: val });
94
    };
Lijiao's avatar
Lijiao committed
95

Lijiaoa's avatar
Lijiaoa committed
96
97
    // overview best trial module
    changeEntries = (entries: string): void => {
Lijiaoa's avatar
Lijiaoa committed
98
        this.setState({ bestTrialEntries: entries });
99
    };
Lijiaoa's avatar
Lijiaoa committed
100

101
102
103
104
105
    // overview max duration unit
    changeMaxDurationUnit = (unit: string): void => {
        this.setState({ maxDurationUnit: unit });
    };

106
107
108
109
110
111
    updateOverviewPage = (): void => {
        this.setState(state => ({
            experimentUpdateBroadcast: state.experimentUpdateBroadcast + 1
        }));
    };

Lijiaoa's avatar
Lijiaoa committed
112
    shouldComponentUpdate(nextProps: any, nextState: AppState): boolean {
113
        if (!(nextState.isUpdate || nextState.isUpdate === undefined)) {
Lijiaoa's avatar
Lijiaoa committed
114
115
116
117
118
119
            nextState.isUpdate = true;
            return false;
        }
        return true;
    }

120
    render(): React.ReactNode {
121
122
123
124
125
126
127
128
        const {
            interval,
            columnList,
            experimentUpdateBroadcast,
            trialsUpdateBroadcast,
            metricGraphMode,
            isillegalFinal,
            expWarningMessage,
129
130
            bestTrialEntries,
            maxDurationUnit
131
        } = this.state;
132
        if (experimentUpdateBroadcast === 0 || trialsUpdateBroadcast === 0) {
133
            return null; // TODO: render a loading page
134
        }
Lijiaoa's avatar
Lijiaoa committed
135
136
137
138
139
140
141
142
        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() }
        ];
143

144
        return (
145
146
147
148
149
150
            <React.Fragment>
                {isManagerExperimentPage() ? null : (
                    <Stack className='nni' style={{ minHeight: window.innerHeight }}>
                        <div className='header'>
                            <div className='headerCon'>
                                <NavCon changeInterval={this.changeInterval} refreshFunction={this.lastRefresh} />
151
                            </div>
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
                        </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,
                                        updateOverviewPage: this.updateOverviewPage
                                    }}
                                >
                                    {this.props.children}
                                </AppContext.Provider>
                            </Stack>
                        </Stack>
191
                    </Stack>
192
193
                )}
            </React.Fragment>
194
195
196
        );
    }

Lijiao's avatar
Lijiao committed
197
    private refresh = async (): Promise<void> => {
198
199
200
201
202
203
204
205
206
207
208
209
        // 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;
210
211
        }

Lijiaoa's avatar
Lijiaoa committed
212
213
        // experiment status and /trial-jobs api's status could decide website update
        if (['DONE', 'ERROR', 'STOPPED'].includes(EXPERIMENT.status) || TRIALS.jobListError()) {
214
            // experiment finished, refresh once more to ensure consistency
Lijiaoa's avatar
Lijiaoa committed
215
            this.setState(() => ({ interval: 0, isUpdate: false }));
216
            return;
217
        }
218

Lijiaoa's avatar
Lijiaoa committed
219
        this.timerId = window.setTimeout(this.refresh, this.state.interval * 1000);
220
    };
221

222
    public async lastRefresh(): Promise<void> {
223
224
        await EXPERIMENT.update();
        await TRIALS.update(true);
225
226
227
228
        this.setState(state => ({
            experimentUpdateBroadcast: state.experimentUpdateBroadcast + 1,
            trialsUpdateBroadcast: state.trialsUpdateBroadcast + 1
        }));
229
    }
Deshui Yu's avatar
Deshui Yu committed
230
231
232
}

export default App;