TableList.tsx 22 KB
Newer Older
Lijiao's avatar
Lijiao committed
1
2
3
import * as React from 'react';
import axios from 'axios';
import ReactEcharts from 'echarts-for-react';
4
5
import { Row, Table, Button, Popconfirm, Modal, Checkbox, Select } from 'antd';
const Option = Select.Option;
6
const CheckboxGroup = Checkbox.Group;
7
import { MANAGER_IP, trialJobStatus, COLUMN, COLUMN_INDEX } from '../../static/const';
Lijiao's avatar
Lijiao committed
8
9
10
import { convertDuration, intermediateGraphOption, killJob } from '../../static/function';
import { TableObj, TrialJob } from '../../static/interface';
import OpenRow from '../public-child/OpenRow';
Lijiao's avatar
Lijiao committed
11
import Compare from '../Modal/Compare';
12
import IntermediateVal from '../public-child/IntermediateVal'; // table default metric column
13
import '../../static/style/search.scss';
Lijiao's avatar
Lijiao committed
14
15
require('../../static/style/tableStatus.css');
require('../../static/style/logPath.scss');
16
require('../../static/style/search.scss');
Lijiao's avatar
Lijiao committed
17
18
require('../../static/style/table.scss');
require('../../static/style/button.scss');
Lijiao's avatar
Lijiao committed
19
require('../../static/style/openRow.scss');
Lijiao's avatar
Lijiao committed
20
21
22
23
24
25
26
27
28
const echarts = require('echarts/lib/echarts');
require('echarts/lib/chart/line');
require('echarts/lib/component/tooltip');
require('echarts/lib/component/title');
echarts.registerTheme('my_theme', {
    color: '#3c8dbc'
});

interface TableListProps {
29
    entries: number;
Lijiao's avatar
Lijiao committed
30
    tableSource: Array<TableObj>;
Lijiao's avatar
Lijiao committed
31
    updateList: Function;
32
    platform: string;
33
    logCollection: boolean;
Lijiao's avatar
Lijiao committed
34
    isMultiPhase: boolean;
Lijiao's avatar
Lijiao committed
35
36
37
38
39
}

interface TableListState {
    intermediateOption: object;
    modalVisible: boolean;
40
41
42
    isObjFinal: boolean;
    isShowColumn: boolean;
    columnSelected: Array<string>; // user select columnKeys
Lijiao's avatar
Lijiao committed
43
44
45
    selectRows: Array<TableObj>;
    isShowCompareModal: boolean;
    selectedRowKeys: string[] | number[];
46
47
48
    intermediateData: Array<object>; // a trial's intermediate results (include dict)
    intermediateId: string;
    intermediateOtherKeys: Array<string>;
49
50
51
52
53
}

interface ColumnIndex {
    name: string;
    index: number;
Lijiao's avatar
Lijiao committed
54
55
56
57
58
}

class TableList extends React.Component<TableListProps, TableListState> {

    public _isMounted = false;
59
60
    public intervalTrialLog = 10;
    public _trialId: string;
Lijiao's avatar
Lijiao committed
61
    public tables: Table<TableObj> | null;
62

Lijiao's avatar
Lijiao committed
63
64
65
66
67
    constructor(props: TableListProps) {
        super(props);

        this.state = {
            intermediateOption: {},
68
69
70
            modalVisible: false,
            isObjFinal: false,
            isShowColumn: false,
Lijiao's avatar
Lijiao committed
71
72
73
            isShowCompareModal: false,
            columnSelected: COLUMN,
            selectRows: [],
74
75
76
77
            selectedRowKeys: [], // close selected trial message after modal closed
            intermediateData: [],
            intermediateId: '',
            intermediateOtherKeys: []
Lijiao's avatar
Lijiao committed
78
79
80
81
82
83
84
85
86
87
88
        };
    }

    showIntermediateModal = (id: string) => {

        axios(`${MANAGER_IP}/metric-data/${id}`, {
            method: 'GET'
        })
            .then(res => {
                if (res.status === 200) {
                    const intermediateArr: number[] = [];
Lijiao's avatar
Lijiao committed
89
90
                    // support intermediate result is dict because the last intermediate result is
                    // final result in a succeed trial, it may be a dict.
91
92
93
94
95
96
                    // get intermediate result dict keys array
                    let otherkeys: Array<string> = ['default'];
                    if (res.data.length !== 0) {
                        otherkeys = Object.keys(JSON.parse(res.data[0].data));
                    }
                    // intermediateArr just store default val
Lijiao's avatar
Lijiao committed
97
                    Object.keys(res.data).map(item => {
Lijiao's avatar
Lijiao committed
98
99
100
101
102
103
                        const temp = JSON.parse(res.data[item].data);
                        if (typeof temp === 'object') {
                            intermediateArr.push(temp.default);
                        } else {
                            intermediateArr.push(temp);
                        }
Lijiao's avatar
Lijiao committed
104
                    });
Lijiao's avatar
Lijiao committed
105
                    const intermediate = intermediateGraphOption(intermediateArr, id);
Lijiao's avatar
Lijiao committed
106
107
                    if (this._isMounted) {
                        this.setState(() => ({
108
109
110
111
                            intermediateData: res.data, // store origin intermediate data for a trial
                            intermediateOption: intermediate,
                            intermediateOtherKeys: otherkeys,
                            intermediateId: id
Lijiao's avatar
Lijiao committed
112
113
114
115
116
117
118
119
120
121
122
                        }));
                    }
                }
            });
        if (this._isMounted) {
            this.setState({
                modalVisible: true
            });
        }
    }

123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
    selectOtherKeys = (value: string) => {

        const isShowDefault: boolean = value === 'default' ? true : false;
        const { intermediateData, intermediateId } = this.state;
        const intermediateArr: number[] = [];
        // just watch default key-val
        if (isShowDefault === true) {
            Object.keys(intermediateData).map(item => {
                const temp = JSON.parse(intermediateData[item].data);
                if (typeof temp === 'object') {
                    intermediateArr.push(temp[value]);
                } else {
                    intermediateArr.push(temp);
                }
            });
        } else {
            Object.keys(intermediateData).map(item => {
                const temp = JSON.parse(intermediateData[item].data);
                if (typeof temp === 'object') {
                    intermediateArr.push(temp[value]);
                }
            });
        }
        const intermediate = intermediateGraphOption(intermediateArr, intermediateId);
        // re-render
        if (this._isMounted) {
            this.setState(() => ({
                intermediateOption: intermediate
            }));
        }
    }

Lijiao's avatar
Lijiao committed
155
156
157
158
159
160
161
162
    hideIntermediateModal = () => {
        if (this._isMounted) {
            this.setState({
                modalVisible: false
            });
        }
    }

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
    hideShowColumnModal = () => {
        if (this._isMounted) {
            this.setState({
                isShowColumn: false
            });
        }
    }

    // click add column btn, just show the modal of addcolumn
    addColumn = () => {
        // show user select check button
        if (this._isMounted) {
            this.setState({
                isShowColumn: true
            });
        }
    }

    // checkbox for coloumn
    selectedColumn = (checkedValues: Array<string>) => {
        let count = 6;
        const want: Array<object> = [];
        const finalKeys: Array<string> = [];
        const wantResult: Array<string> = [];
        Object.keys(checkedValues).map(m => {
            switch (checkedValues[m]) {
Lijiao's avatar
Lijiao committed
189
                case 'Trial No.':
Lijiao's avatar
Lijiao committed
190
                case 'ID':
Lijiao's avatar
Lijiao committed
191
192
                case 'Duration':
                case 'Status':
193
194
                case 'Operation':
                case 'Default':
Lijiao's avatar
Lijiao committed
195
                case 'Intermediate result':
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
                    break;
                default:
                    finalKeys.push(checkedValues[m]);
            }
        });

        Object.keys(finalKeys).map(n => {
            want.push({
                name: finalKeys[n],
                index: count++
            });
        });

        Object.keys(checkedValues).map(item => {
            const temp = checkedValues[item];
            Object.keys(COLUMN_INDEX).map(key => {
                const index = COLUMN_INDEX[key];
                if (index.name === temp) {
                    want.push(index);
                }
            });
        });

        want.sort((a: ColumnIndex, b: ColumnIndex) => {
            return a.index - b.index;
        });

        Object.keys(want).map(i => {
            wantResult.push(want[i].name);
        });

        if (this._isMounted) {
            this.setState(() => ({ columnSelected: wantResult }));
        }
    }

Lijiao's avatar
Lijiao committed
232
    openRow = (record: TableObj) => {
Lijiao's avatar
Lijiao committed
233
        const { platform, logCollection, isMultiPhase } = this.props;
Lijiao's avatar
Lijiao committed
234
235
236
237
        return (
            <OpenRow
                trainingPlatform={platform}
                record={record}
238
                logCollection={logCollection}
Lijiao's avatar
Lijiao committed
239
                multiphase={isMultiPhase}
Lijiao's avatar
Lijiao committed
240
241
242
243
            />
        );
    }

Lijiao's avatar
Lijiao committed
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
    fillSelectedRowsTostate = (selected: number[] | string[], selectedRows: Array<TableObj>) => {
        if (this._isMounted === true) {
            this.setState(() => ({ selectRows: selectedRows, selectedRowKeys: selected }));
        }
    }
    // open Compare-modal
    compareBtn = () => {

        const { selectRows } = this.state;
        if (selectRows.length === 0) {
            alert('Please select datas you want to compare!');
        } else {
            if (this._isMounted === true) {
                this.setState({ isShowCompareModal: true });
            }
        }
    }
    // close Compare-modal
    hideCompareModal = () => {
        // close modal. clear select rows data, clear selected track
        if (this._isMounted) {
            this.setState({ isShowCompareModal: false, selectedRowKeys: [], selectRows: [] });
        }
    }

Lijiao's avatar
Lijiao committed
269
270
271
272
273
274
275
276
277
    componentDidMount() {
        this._isMounted = true;
    }

    componentWillUnmount() {
        this._isMounted = false;
    }

    render() {
278

279
        const { entries, tableSource, updateList } = this.props;
Lijiao's avatar
Lijiao committed
280
        const { intermediateOption, modalVisible, isShowColumn, columnSelected,
281
            selectRows, isShowCompareModal, selectedRowKeys, intermediateOtherKeys } = this.state;
Lijiao's avatar
Lijiao committed
282
283
284
285
286
287
        const rowSelection = {
            selectedRowKeys: selectedRowKeys,
            onChange: (selected: string[] | number[], selectedRows: Array<TableObj>) => {
                this.fillSelectedRowsTostate(selected, selectedRows);
            }
        };
288
        let showTitle = COLUMN;
289
290
291
        let bgColor = '';
        const trialJob: Array<TrialJob> = [];
        const showColumn: Array<object> = [];
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
        if (tableSource.length >= 1) {
            const temp = tableSource[0].acc;
            if (temp !== undefined && typeof temp === 'object') {
                if (this._isMounted) {
                    // concat default column and finalkeys
                    const item = Object.keys(temp);
                    const want: Array<string> = [];
                    Object.keys(item).map(key => {
                        if (item[key] !== 'default') {
                            want.push(item[key]);
                        }
                    });
                    showTitle = COLUMN.concat(want);
                }
            }
        }
Lijiao's avatar
Lijiao committed
308
309
310
311
312
313
        trialJobStatus.map(item => {
            trialJob.push({
                text: item,
                value: item
            });
        });
314
315
316
        Object.keys(columnSelected).map(key => {
            const item = columnSelected[key];
            switch (item) {
Lijiao's avatar
Lijiao committed
317
                case 'Trial No.':
318
319
320
321
322
323
                    showColumn.push({
                        title: 'Trial No.',
                        dataIndex: 'sequenceId',
                        key: 'sequenceId',
                        width: 120,
                        className: 'tableHead',
324
                        sorter: (a: TableObj, b: TableObj) => (a.sequenceId as number) - (b.sequenceId as number)
325
326
                    });
                    break;
Lijiao's avatar
Lijiao committed
327
                case 'ID':
328
                    showColumn.push({
Lijiao's avatar
Lijiao committed
329
                        title: 'ID',
330
331
332
                        dataIndex: 'id',
                        key: 'id',
                        width: 60,
Lijiao's avatar
Lijiao committed
333
                        className: 'tableHead leftTitle',
334
                        // the sort of string
Lijiao's avatar
Lijiao committed
335
336
                        sorter: (a: TableObj, b: TableObj): number => a.id.localeCompare(b.id),
                        render: (text: string, record: TableObj) => {
337
338
339
340
341
342
                            return (
                                <div>{record.id}</div>
                            );
                        }
                    });
                    break;
Lijiao's avatar
Lijiao committed
343
                case 'Duration':
344
345
346
347
348
349
                    showColumn.push({
                        title: 'Duration',
                        dataIndex: 'duration',
                        key: 'duration',
                        width: 140,
                        // the sort of number
Lijiao's avatar
Lijiao committed
350
351
                        sorter: (a: TableObj, b: TableObj) => (a.duration as number) - (b.duration as number),
                        render: (text: string, record: TableObj) => {
352
                            let duration;
353
                            if (record.duration !== undefined) {
354
355
                                // duration is nagative number(-1) & 0-1
                                if (record.duration > 0 && record.duration < 1 || record.duration < 0) {
356
357
358
359
                                    duration = `${record.duration}s`;
                                } else {
                                    duration = convertDuration(record.duration);
                                }
360
361
362
363
364
365
366
367
368
                            } else {
                                duration = 0;
                            }
                            return (
                                <div className="durationsty"><div>{duration}</div></div>
                            );
                        },
                    });
                    break;
Lijiao's avatar
Lijiao committed
369
                case 'Status':
370
371
372
373
374
375
                    showColumn.push({
                        title: 'Status',
                        dataIndex: 'status',
                        key: 'status',
                        width: 150,
                        className: 'tableStatus',
Lijiao's avatar
Lijiao committed
376
                        render: (text: string, record: TableObj) => {
377
378
379
380
381
382
                            bgColor = record.status;
                            return (
                                <span className={`${bgColor} commonStyle`}>{record.status}</span>
                            );
                        },
                        filters: trialJob,
383
384
385
386
                        onFilter: (value: string, record: TableObj) => {
                            return record.status.indexOf(value) === 0;
                        },
                        // onFilter: (value: string, record: TableObj) => record.status.indexOf(value) === 0,
Lijiao's avatar
Lijiao committed
387
                        sorter: (a: TableObj, b: TableObj): number => a.status.localeCompare(b.status)
388
389
390
391
                    });
                    break;
                case 'Default':
                    showColumn.push({
Lijiao's avatar
Lijiao committed
392
393
                        title: 'Default metric',
                        className: 'leftTitle',
394
395
                        dataIndex: 'acc',
                        key: 'acc',
Lijiao's avatar
Lijiao committed
396
                        width: 120,
Lijiao's avatar
Lijiao committed
397
                        sorter: (a: TableObj, b: TableObj) => {
398
399
400
401
                            const aa = a.description.intermediate;
                            const bb = b.description.intermediate;
                            if (aa !== undefined && bb !== undefined) {
                                return aa[aa.length - 1] - bb[bb.length - 1];
402
403
404
405
                            } else {
                                return NaN;
                            }
                        },
Lijiao's avatar
Lijiao committed
406
                        render: (text: string, record: TableObj) => {
407
                            return (
408
                                <IntermediateVal record={record} />
409
410
411
412
413
414
415
416
417
418
                            );
                        }
                    });
                    break;
                case 'Operation':
                    showColumn.push({
                        title: 'Operation',
                        dataIndex: 'operation',
                        key: 'operation',
                        width: 90,
Lijiao's avatar
Lijiao committed
419
                        render: (text: string, record: TableObj) => {
420
421
422
423
424
425
426
427
428
                            let trialStatus = record.status;
                            let flagKill = false;
                            if (trialStatus === 'RUNNING') {
                                flagKill = true;
                            } else {
                                flagKill = false;
                            }
                            return (
                                flagKill
429
                                    ?
430
431
432
                                    (
                                        <Popconfirm
                                            title="Are you sure to cancel this trial?"
Lijiao's avatar
Lijiao committed
433
434
                                            onConfirm={killJob.
                                                bind(this, record.key, record.id, record.status, updateList)}
435
436
437
438
                                        >
                                            <Button type="primary" className="tableButton">Kill</Button>
                                        </Popconfirm>
                                    )
439
                                    :
440
441
442
443
444
445
446
447
448
449
450
451
452
453
                                    (
                                        <Button
                                            type="primary"
                                            className="tableButton"
                                            disabled={true}
                                        >
                                            Kill
                                        </Button>
                                    )
                            );
                        },
                    });
                    break;

Lijiao's avatar
Lijiao committed
454
                case 'Intermediate result':
455
                    showColumn.push({
Lijiao's avatar
Lijiao committed
456
                        title: 'Intermediate result',
457
458
459
                        dataIndex: 'intermediate',
                        key: 'intermediate',
                        width: '16%',
Lijiao's avatar
Lijiao committed
460
                        render: (text: string, record: TableObj) => {
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
                            return (
                                <Button
                                    type="primary"
                                    className="tableButton"
                                    onClick={this.showIntermediateModal.bind(this, record.id)}
                                >
                                    Intermediate
                                </Button>
                            );
                        },
                    });
                    break;
                default:
                    showColumn.push({
                        title: item,
                        dataIndex: item,
                        key: item,
                        width: 150,
Lijiao's avatar
Lijiao committed
479
                        render: (text: string, record: TableObj) => {
480
481
482
483
484
485
486
487
488
489
                            const temp = record.acc;
                            let decimals = 0;
                            let other = '';
                            if (temp !== undefined) {
                                if (temp[item].toString().indexOf('.') !== -1) {
                                    decimals = temp[item].toString().length - temp[item].toString().indexOf('.') - 1;
                                    if (decimals > 6) {
                                        other = `${temp[item].toFixed(6)}`;
                                    } else {
                                        other = temp[item].toString();
490
                                    }
491
492
493
494
495
496
                                }
                            } else {
                                other = '--';
                            }
                            return (
                                <div>{other}</div>
497
                            );
Lijiao's avatar
Lijiao committed
498
                        }
499
                    });
Lijiao's avatar
Lijiao committed
500
            }
501
        });
Lijiao's avatar
Lijiao committed
502
503
504
505
506

        return (
            <Row className="tableList">
                <div id="tableList">
                    <Table
Lijiao's avatar
Lijiao committed
507
                        ref={(table: Table<TableObj> | null) => this.tables = table}
508
                        columns={showColumn}
Lijiao's avatar
Lijiao committed
509
                        rowSelection={rowSelection}
Lijiao's avatar
Lijiao committed
510
                        expandedRowRender={this.openRow}
511
                        dataSource={tableSource}
Lijiao's avatar
Lijiao committed
512
                        className="commonTableStyle"
513
                        pagination={{ pageSize: entries }}
Lijiao's avatar
Lijiao committed
514
                    />
515
                    {/* Intermediate Result Modal */}
Lijiao's avatar
Lijiao committed
516
                    <Modal
Lijiao's avatar
Lijiao committed
517
                        title="Intermediate result"
Lijiao's avatar
Lijiao committed
518
519
520
521
522
523
                        visible={modalVisible}
                        onCancel={this.hideIntermediateModal}
                        footer={null}
                        destroyOnClose={true}
                        width="80%"
                    >
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
                        {
                            intermediateOtherKeys.length > 1
                                ?
                                <Row className="selectKeys">
                                    <Select
                                        className="select"
                                        defaultValue="default"
                                        onSelect={this.selectOtherKeys}
                                    >
                                        {
                                            Object.keys(intermediateOtherKeys).map(item => {
                                                const keys = intermediateOtherKeys[item];
                                                return <Option value={keys} key={item}>{keys}</Option>;
                                            })
                                        }
                                    </Select>

                                </Row>
                                :
                                <div />
                        }
Lijiao's avatar
Lijiao committed
545
546
547
548
549
550
551
552
553
554
                        <ReactEcharts
                            option={intermediateOption}
                            style={{
                                width: '100%',
                                height: 0.7 * window.innerHeight
                            }}
                            theme="my_theme"
                        />
                    </Modal>
                </div>
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
                {/* Add Column Modal */}
                <Modal
                    title="Table Title"
                    visible={isShowColumn}
                    onCancel={this.hideShowColumnModal}
                    footer={null}
                    destroyOnClose={true}
                    width="40%"
                >
                    <CheckboxGroup
                        options={showTitle}
                        defaultValue={columnSelected}
                        onChange={this.selectedColumn}
                        className="titleColumn"
                    />
                </Modal>
Lijiao's avatar
Lijiao committed
571
                <Compare compareRows={selectRows} visible={isShowCompareModal} cancelFunc={this.hideCompareModal} />
Lijiao's avatar
Lijiao committed
572
573
574
575
576
            </Row>
        );
    }
}

577
export default TableList;