TableList.tsx 25.2 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
6
7
8
9
10
import {
    Stack, Dropdown, DetailsList, IDetailsListProps, DetailsListLayoutMode,
    PrimaryButton, Modal, IDropdownOption, IColumn, Selection, SelectionMode, IconButton
} from 'office-ui-fabric-react';
import { LineChart, blocked, copy } from '../Buttons/Icon';
import { MANAGER_IP, COLUMNPro } from '../../static/const';
import { convertDuration, formatTimestamp, intermediateGraphOption, parseMetrics } from '../../static/function';
11
import { EXPERIMENT, TRIALS } from '../../static/datamodel';
12
import { TableRecord } from '../../static/interface';
13
14
15
16
17
18
import Details from '../overview/Details';
import ChangeColumnComponent from '../Modals/ChangeColumnComponent';
import Compare from '../Modals/Compare';
import KillJob from '../Modals/Killjob';
import Customize from '../Modals/CustomizedTrial';
import { contentStyles, iconButtonStyles } from '../Buttons/ModalTheme';
19
import '../../static/style/search.scss';
20
21
22
23
24
25
import '../../static/style/tableStatus.css';
import '../../static/style/logPath.scss';
import '../../static/style/search.scss';
import '../../static/style/table.scss';
import '../../static/style/button.scss';
import '../../static/style/openRow.scss';
Lijiao's avatar
Lijiao committed
26
27
28
29
30
31
32
33
34
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 {
35
36
    pageSize: number;
    tableSource: Array<TableRecord>;
37
38
    columnList: string[]; // user select columnKeys
    changeColumn: (val: string[]) => void;
39
    trialsUpdateBroadcast: number;
Lijiao's avatar
Lijiao committed
40
41
42
43
44
}

interface TableListState {
    intermediateOption: object;
    modalVisible: boolean;
45
46
    isObjFinal: boolean;
    isShowColumn: boolean;
47
    selectRows: Array<any>;
Lijiao's avatar
Lijiao committed
48
49
    isShowCompareModal: boolean;
    selectedRowKeys: string[] | number[];
50
51
    intermediateData: Array<object>; // a trial's intermediate results (include dict)
    intermediateId: string;
52
    intermediateOtherKeys: string[];
53
54
    isShowCustomizedModal: boolean;
    copyTrialId: string; // user copy trial to submit a new customized trial
55
    isCalloutVisible: boolean; // kill job button callout [kill or not kill job window]
56
    intermediateKey: string; // intermeidate modal: which key is choosed.
57
58
59
60
61
62
    isExpand: boolean;
    modalIntermediateWidth: number;
    modalIntermediateHeight: number;
    tableColumns: IColumn[];
    allColumnList: string[];
    tableSourceForSort: Array<TableRecord>;
63
64
}

Lijiao's avatar
Lijiao committed
65

Lijiao's avatar
Lijiao committed
66
67
class TableList extends React.Component<TableListProps, TableListState> {

68
    public intervalTrialLog = 10;
69
    public trialId!: string;
70

Lijiao's avatar
Lijiao committed
71
72
73
74
75
    constructor(props: TableListProps) {
        super(props);

        this.state = {
            intermediateOption: {},
76
77
78
            modalVisible: false,
            isObjFinal: false,
            isShowColumn: false,
Lijiao's avatar
Lijiao committed
79
80
            isShowCompareModal: false,
            selectRows: [],
81
82
83
            selectedRowKeys: [], // close selected trial message after modal closed
            intermediateData: [],
            intermediateId: '',
84
85
            intermediateOtherKeys: [],
            isShowCustomizedModal: false,
86
87
            isCalloutVisible: false,
            copyTrialId: '',
88
            intermediateKey: 'default',
89
90
91
92
93
94
            isExpand: false,
            modalIntermediateWidth: window.innerWidth,
            modalIntermediateHeight: window.innerHeight,
            tableColumns: this.initTableColumnList(this.props.columnList),
            allColumnList: this.getAllColumnKeys(),
            tableSourceForSort: this.props.tableSource
Lijiao's avatar
Lijiao committed
95
96
97
        };
    }

98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
    // sort for table column
    onColumnClick = (ev: React.MouseEvent<HTMLElement>, getColumn: IColumn): void => {
        const { tableColumns } = this.state;
        const { tableSource } = this.props;
        const newColumns: IColumn[] = tableColumns.slice();
        const currColumn: IColumn = newColumns.filter(item => getColumn.key === item.key)[0];
        newColumns.forEach((newCol: IColumn) => {
            if (newCol === currColumn) {
                currColumn.isSortedDescending = !currColumn.isSortedDescending;
                currColumn.isSorted = true;
            } else {
                newCol.isSorted = false;
                newCol.isSortedDescending = true;
            }
        });
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        const newItems = this.copyAndSort(tableSource, currColumn.fieldName!, currColumn.isSortedDescending);
        this.setState({
            tableColumns: newColumns,
            tableSourceForSort: newItems
        });
    };

    private copyAndSort<T>(items: T[], columnKey: string, isSortedDescending?: boolean): T[] {
        const key = columnKey as keyof T;
        return items.slice(0).sort((a: T, b: T) => ((isSortedDescending ? a[key] < b[key] : a[key] > b[key]) ? 1 : -1));
    }

    AccuracyColumnConfig: any = {
        name: 'Default metric',
        className: 'leftTitle',
        key: 'accuracy',
130
        fieldName: 'latestAccuracy',
131
132
133
134
135
136
137
138
139
140
141
142
143
        minWidth: 200,
        maxWidth: 300,
        isResizable: true,
        data: 'number',
        onColumnClick: this.onColumnClick,
        onRender: (item): React.ReactNode => <div>{item.formattedLatestAccuracy}</div>
    };

    SequenceIdColumnConfig: any = {
        name: 'Trial No.',
        key: 'sequenceId',
        fieldName: 'sequenceId',
        minWidth: 80,
144
        maxWidth: 240,
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
        className: 'tableHead',
        data: 'string',
        onColumnClick: this.onColumnClick,
    };

    IdColumnConfig: any = {
        name: 'ID',
        key: 'id',
        fieldName: 'id',
        minWidth: 150,
        maxWidth: 200,
        isResizable: true,
        data: 'string',
        onColumnClick: this.onColumnClick,
        className: 'tableHead leftTitle'
    };


    StartTimeColumnConfig: any = {
        name: 'Start Time',
        key: 'startTime',
        fieldName: 'startTime',
        minWidth: 150,
168
        maxWidth: 400,
169
170
171
172
173
174
175
176
177
178
179
180
        isResizable: true,
        data: 'number',
        onColumnClick: this.onColumnClick,
        onRender: (record): React.ReactNode => (
            <span>{formatTimestamp(record.startTime)}</span>
        )
    };

    EndTimeColumnConfig: any = {
        name: 'End Time',
        key: 'endTime',
        fieldName: 'endTime',
181
182
        minWidth: 200,
        maxWidth: 400,
183
184
185
186
187
188
189
190
191
192
193
194
195
        isResizable: true,
        data: 'number',
        onColumnClick: this.onColumnClick,
        onRender: (record): React.ReactNode => (
            <span>{formatTimestamp(record.endTime, '--')}</span>
        )
    };

    DurationColumnConfig: any = {
        name: 'Duration',
        key: 'duration',
        fieldName: 'duration',
        minWidth: 150,
196
        maxWidth: 300,
197
198
199
200
201
202
203
204
205
206
207
208
209
210
        isResizable: true,
        data: 'number',
        onColumnClick: this.onColumnClick,
        onRender: (record): React.ReactNode => (
            <span className="durationsty">{convertDuration(record.duration)}</span>
        )
    };

    StatusColumnConfig: any = {
        name: 'Status',
        key: 'status',
        fieldName: 'status',
        className: 'tableStatus',
        minWidth: 150,
211
        maxWidth: 250,
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
        isResizable: true,
        data: 'string',
        onColumnClick: this.onColumnClick,
        onRender: (record): React.ReactNode => (
            <span className={`${record.status} commonStyle`}>{record.status}</span>
        ),
    };

    IntermediateCountColumnConfig: any = {
        name: 'Intermediate result',
        dataIndex: 'intermediateCount',
        fieldName: 'intermediateCount',
        minWidth: 150,
        maxWidth: 200,
        isResizable: true,
        data: 'number',
        onColumnClick: this.onColumnClick,
        onRender: (record): React.ReactNode => (
            <span>{`#${record.intermediateCount}`}</span>
        )
    };

    showIntermediateModal = async (id: string, event: React.SyntheticEvent<EventTarget>): Promise<void> => {
        event.preventDefault();
        event.stopPropagation();
237
238
239
240
241
242
        const res = await axios.get(`${MANAGER_IP}/metric-data/${id}`);
        if (res.status === 200) {
            const intermediateArr: number[] = [];
            // support intermediate result is dict because the last intermediate result is
            // final result in a succeed trial, it may be a dict.
            // get intermediate result dict keys array
Lijiaoa's avatar
Lijiaoa committed
243
            const { intermediateKey } = this.state;
244
            let otherkeys: string[] = ['default'];
245
            if (res.data.length !== 0) {
chicm-ms's avatar
chicm-ms committed
246
                otherkeys = Object.keys(parseMetrics(res.data[0].data));
247
248
249
            }
            // intermediateArr just store default val
            Object.keys(res.data).map(item => {
250
                if (res.data[item].type === 'PERIODICAL') {
251
252
253
254
255
256
                    const temp = parseMetrics(res.data[item].data);
                    if (typeof temp === 'object') {
                        intermediateArr.push(temp[intermediateKey]);
                    } else {
                        intermediateArr.push(temp);
                    }
Lijiao's avatar
Lijiao committed
257
258
                }
            });
259
            const intermediate = intermediateGraphOption(intermediateArr, id);
Lijiao's avatar
Lijiao committed
260
            this.setState({
261
262
263
264
                intermediateData: res.data, // store origin intermediate data for a trial
                intermediateOption: intermediate,
                intermediateOtherKeys: otherkeys,
                intermediateId: id
Lijiao's avatar
Lijiao committed
265
266
            });
        }
267
        this.setState({ modalVisible: true });
Lijiao's avatar
Lijiao committed
268
269
    }

270
271
    // intermediate button click -> intermediate graph for each trial
    // support intermediate is dict
272
273
274
275
276
277
278
279
280
    selectOtherKeys = (event: React.FormEvent<HTMLDivElement>, item?: IDropdownOption): void => {
        if (item !== undefined) {
            const value = item.text;
            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 => {
281
282
283
284
285
286
287
                    if (intermediateData[item].type === 'PERIODICAL') {
                        const temp = parseMetrics(intermediateData[item].data);
                        if (typeof temp === 'object') {
                            intermediateArr.push(temp[value]);
                        } else {
                            intermediateArr.push(temp);
                        }
288
289
290
291
292
293
294
295
296
297
298
299
300
                    }
                });
            } else {
                Object.keys(intermediateData).map(item => {
                    const temp = parseMetrics(intermediateData[item].data);
                    if (typeof temp === 'object') {
                        intermediateArr.push(temp[value]);
                    }
                });
            }
            const intermediate = intermediateGraphOption(intermediateArr, intermediateId);
            // re-render
            this.setState({
301
                intermediateKey: value,
302
                intermediateOption: intermediate
303
304
305
306
            });
        }
    }

Lijiao's avatar
Lijiao committed
307
    hideIntermediateModal = (): void => {
308
309
310
        this.setState({
            modalVisible: false
        });
Lijiao's avatar
Lijiao committed
311
312
    }

Lijiao's avatar
Lijiao committed
313
    hideShowColumnModal = (): void => {
314
315

        this.setState(() => ({ isShowColumn: false }));
316
317
318
    }

    // click add column btn, just show the modal of addcolumn
Lijiao's avatar
Lijiao committed
319
    addColumn = (): void => {
320
        // show user select check button
321
        this.setState(() => ({ isShowColumn: true }));
Lijiao's avatar
Lijiao committed
322
323
    }

Lijiao's avatar
Lijiao committed
324
    fillSelectedRowsTostate = (selected: number[] | string[], selectedRows: Array<TableRecord>): void => {
325
        this.setState({ selectRows: selectedRows, selectedRowKeys: selected });
Lijiao's avatar
Lijiao committed
326
    }
327

Lijiao's avatar
Lijiao committed
328
    // open Compare-modal
Lijiao's avatar
Lijiao committed
329
    compareBtn = (): void => {
Lijiao's avatar
Lijiao committed
330
331
332
333
334

        const { selectRows } = this.state;
        if (selectRows.length === 0) {
            alert('Please select datas you want to compare!');
        } else {
335
            this.setState({ isShowCompareModal: true });
Lijiao's avatar
Lijiao committed
336
337
        }
    }
338

Lijiao's avatar
Lijiao committed
339
    // close Compare-modal
Lijiao's avatar
Lijiao committed
340
    hideCompareModal = (): void => {
Lijiao's avatar
Lijiao committed
341
        // close modal. clear select rows data, clear selected track
342
        this.setState({ isShowCompareModal: false, selectedRowKeys: [], selectRows: [] });
Lijiao's avatar
Lijiao committed
343
344
    }

345
    // open customized trial modal
346
347
348
    private setCustomizedTrial = (trialId: string, event: React.SyntheticEvent<EventTarget>): void => {
        event.preventDefault();
        event.stopPropagation();
349
350
351
352
353
354
        this.setState({
            isShowCustomizedModal: true,
            copyTrialId: trialId
        });
    }

355
    private closeCustomizedTrial = (): void => {
356
357
358
359
360
        this.setState({
            isShowCustomizedModal: false,
            copyTrialId: ''
        });
    }
361

362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
    private onWindowResize = (): void => {
        this.setState(() => ({
            modalIntermediateHeight: window.innerHeight,
            modalIntermediateWidth: window.innerWidth
        }));
    }

    private onRenderRow: IDetailsListProps['onRenderRow'] = props => {
        if (props) {
            return <Details detailsProps={props} />;
        }
        return null;
    };

    private getSelectedRows = new Selection({
        onSelectionChanged: (): void => {
            this.setState(() => ({ selectRows: this.getSelectedRows.getSelection() }));
        }
    });

    // trial parameters & dict final keys & Trial No. Id ...
    private getAllColumnKeys = (): string[] => {
        const tableSource: Array<TableRecord> = JSON.parse(JSON.stringify(this.props.tableSource));
385
        // parameter as table column
386
        const parameterStr: string[] = [];
387
388
389
        if (tableSource.length > 0) {
            const trialMess = TRIALS.getTrial(tableSource[0].id);
            const trial = trialMess.description.parameters;
390
            const parameterColumn: string[] = Object.keys(trial);
391
392
393
394
            parameterColumn.forEach(value => {
                parameterStr.push(`${value} (search space)`);
            });
        }
395
        let allColumnList = COLUMNPro.concat(parameterStr);
396

397
        // only succeed trials have final keys
398
        if (tableSource.filter(record => record.status === 'SUCCEEDED').length >= 1) {
399
            const temp = tableSource.filter(record => record.status === 'SUCCEEDED')[0].accDictionary;
400
            if (temp !== undefined && typeof temp === 'object') {
401
402
403
404
405
406
407
408
409
410
411
                // concat default column and finalkeys
                const item = Object.keys(temp);
                // item: ['default', 'other-keys', 'maybe loss']
                if (item.length > 1) {
                    const want: string[] = [];
                    item.forEach(value => {
                        if (value !== 'default') {
                            want.push(value);
                        }
                    });
                    allColumnList = allColumnList.concat(want);
412
                }
413
414
            }
        }
415

416
417
418
419
420
421
422
423
424
425
426
        return allColumnList;
    }

    // get IColumn[]
    // when user click [Add Column] need to use the function
    private initTableColumnList = (columnList: string[]): IColumn[] => {
        // const { columnList } = this.props;
        // [supportCustomizedTrial: true]
        const supportCustomizedTrial = (EXPERIMENT.multiPhase === true) ? false : true;
        const disabledAddCustomizedTrial = ['DONE', 'ERROR', 'STOPPED'].includes(EXPERIMENT.status);
        const showColumn: IColumn[] = [];
427
        for (const item of columnList) {
428
            const paraColumn = item.match(/ \(search space\)$/);
429
            let result;
430
            if (paraColumn !== null) {
431
                result = paraColumn.input;
432
            }
433
            switch (item) {
Lijiao's avatar
Lijiao committed
434
                case 'Trial No.':
435
                    showColumn.push(this.SequenceIdColumnConfig);
436
                    break;
Lijiao's avatar
Lijiao committed
437
                case 'ID':
438
                    showColumn.push(this.IdColumnConfig);
439
                    break;
440
                case 'Start Time':
441
                    showColumn.push(this.StartTimeColumnConfig);
442
                    break;
443
                case 'End Time':
444
                    showColumn.push(this.EndTimeColumnConfig);
445
                    break;
Lijiao's avatar
Lijiao committed
446
                case 'Duration':
447
                    showColumn.push(this.DurationColumnConfig);
448
                    break;
Lijiao's avatar
Lijiao committed
449
                case 'Status':
450
                    showColumn.push(this.StatusColumnConfig);
451
                    break;
452
                case 'Intermediate result':
453
                    showColumn.push(this.IntermediateCountColumnConfig);
454
                    break;
455
                case 'Default':
456
                    showColumn.push(this.AccuracyColumnConfig);
457
458
459
                    break;
                case 'Operation':
                    showColumn.push({
460
                        name: 'Operation',
461
                        key: 'operation',
462
463
464
465
466
467
                        fieldName: 'operation',
                        minWidth: 160,
                        maxWidth: 200,
                        isResizable: true,
                        className: 'detail-table',
                        onRender: (record: any) => {
Lijiao's avatar
Lijiao committed
468
                            const trialStatus = record.status;
Lijiao's avatar
Lijiao committed
469
                            const flag: boolean = (trialStatus === 'RUNNING' || trialStatus === 'UNKNOWN') ? false : true;
470
                            return (
471
                                <Stack className="detail-button" horizontal>
472
                                    {/* see intermediate result graph */}
473
474
                                    <PrimaryButton
                                        className="detail-button-operation"
475
                                        title="Intermediate"
476
                                        onClick={this.showIntermediateModal.bind(this, record.id)}
477
                                    >
478
479
                                        {LineChart}
                                    </PrimaryButton>
480
                                    {/* kill job */}
Lijiao's avatar
Lijiao committed
481
482
483
                                    {
                                        flag
                                            ?
484
485
486
                                            <PrimaryButton className="detail-button-operation" disabled={true} title="kill">
                                                {blocked}
                                            </PrimaryButton>
Lijiao's avatar
Lijiao committed
487
                                            :
488
                                            <KillJob trial={record} />
Lijiao's avatar
Lijiao committed
489
                                    }
490
491
492
493
                                    {/* Add a new trial-customized trial */}
                                    {
                                        supportCustomizedTrial
                                            ?
494
495
                                            <PrimaryButton
                                                className="detail-button-operation"
496
                                                title="Customized trial"
497
498
                                                onClick={this.setCustomizedTrial.bind(this, record.id)}
                                                disabled={disabledAddCustomizedTrial}
499
                                            >
500
501
                                                {copy}
                                            </PrimaryButton>
502
503
504
                                            :
                                            null
                                    }
505
                                </Stack>
506
507
508
509
                            );
                        },
                    });
                    break;
510
                case (result):
511
                    // remove SEARCH_SPACE title
Lijiao's avatar
Lijiao committed
512
                    // const realItem = item.replace(' (search space)', '');
513
                    showColumn.push({
514
                        name: item.replace(' (search space)', ''),
515
                        key: item,
516
517
518
                        fieldName: item,
                        minWidth: 150,
                        onRender: (record: TableRecord) => {
519
                            const eachTrial = TRIALS.getTrial(record.id);
520
                            return (
Lijiao's avatar
Lijiao committed
521
                                <span>{eachTrial.description.parameters[item.replace(' (search space)', '')]}</span>
522
523
524
525
526
                            );
                        },
                    });
                    break;
                default:
527
528
529
530
531
532
533
534
535
                    showColumn.push({
                        name: item,
                        key: item,
                        fieldName: item,
                        minWidth: 100,
                        onRender: (record: TableRecord) => {
                            const accDictionary = record.accDictionary;
                            let other = '';
                            if (accDictionary !== undefined) {
Lijiao's avatar
Lijiao committed
536
                                other = accDictionary[item].toString();
537
538
539
540
541
542
                            }
                            return (
                                <div>{other}</div>
                            );
                        }
                    });
Lijiao's avatar
Lijiao committed
543
            }
544
        }
545
546
547
548
549
550
551
552
        return showColumn;
    }

    componentDidMount(): void {
        window.addEventListener('resize', this.onWindowResize);
    }

    UNSAFE_componentWillReceiveProps(nextProps: TableListProps): void {
553
        const { columnList, tableSource } = nextProps;
Lijiao's avatar
Lijiao committed
554
555
556
557
558
        this.setState({
            tableSourceForSort: tableSource,
            tableColumns: this.initTableColumnList(columnList),
            allColumnList: this.getAllColumnKeys()
        });
559
560
561

    }
    render(): React.ReactNode {
562
        const { intermediateKey, modalIntermediateWidth, modalIntermediateHeight,
563
564
565
566
567
568
            tableColumns, allColumnList, isShowColumn, modalVisible,
            selectRows, isShowCompareModal, intermediateOtherKeys,
            isShowCustomizedModal, copyTrialId, intermediateOption
        } = this.state;
        const { columnList } = this.props;
        const tableSource: Array<TableRecord> = JSON.parse(JSON.stringify(this.state.tableSourceForSort));
Lijiao's avatar
Lijiao committed
569
        return (
570
            <Stack>
Lijiao's avatar
Lijiao committed
571
                <div id="tableList">
572
573
574
575
576
577
578
579
580
                    <DetailsList
                        columns={tableColumns}
                        items={tableSource}
                        setKey="set"
                        compact={true}
                        onRenderRow={this.onRenderRow}
                        layoutMode={DetailsListLayoutMode.justified}
                        selectionMode={SelectionMode.multiple}
                        selection={this.getSelectedRows}
Lijiao's avatar
Lijiao committed
581
                    />
582

Lijiao's avatar
Lijiao committed
583
                </div>
584
                {/* Intermediate Result Modal */}
585
                <Modal
586
587
588
                    isOpen={modalVisible}
                    onDismiss={this.hideIntermediateModal}
                    containerClassName={contentStyles.container}
589
                >
590
591
592
593
594
595
596
597
598
599
600
601
                    <div className={contentStyles.header}>
                        <span>Intermediate result</span>
                        <IconButton
                            styles={iconButtonStyles}
                            iconProps={{ iconName: 'Cancel' }}
                            ariaLabel="Close popup modal"
                            onClick={this.hideIntermediateModal as any}
                        />
                    </div>
                    {
                        intermediateOtherKeys.length > 1
                            ?
602
                            <Stack horizontalAlign="end" className="selectKeys">
603
604
                                <Dropdown
                                    className="select"
605
                                    selectedKey={intermediateKey}
606
607
608
609
610
611
612
                                    options={
                                        intermediateOtherKeys.map((key, item) => {
                                            return {
                                                key: key, text: intermediateOtherKeys[item]
                                            };
                                        })
                                    }
613
                                    onChange={this.selectOtherKeys}
614
615
616
617
618
619
620
621
622
623
624
625
626
                                />
                            </Stack>
                            :
                            null
                    }
                    <ReactEcharts
                        option={intermediateOption}
                        style={{
                            width: 0.5 * modalIntermediateWidth,
                            height: 0.7 * modalIntermediateHeight,
                            padding: 20
                        }}
                        theme="my_theme"
627
628
                    />
                </Modal>
629
630
631
632
633
634
635
636
637
638
639
                {/* Add Column Modal */}
                {
                    isShowColumn &&
                    <ChangeColumnComponent
                        hideShowColumnDialog={this.hideShowColumnModal}
                        isHideDialog={!isShowColumn}
                        showColumn={allColumnList}
                        selectedColumn={columnList}
                        changeColumn={this.props.changeColumn}
                    />
                }
640
                {/* compare trials based message */}
641
                {isShowCompareModal && <Compare compareStacks={selectRows} cancelFunc={this.hideCompareModal} />}
642
643
644
645
646
647
                {/* clone trial parameters and could submit a customized trial */}
                <Customize
                    visible={isShowCustomizedModal}
                    copyTrialId={copyTrialId}
                    closeCustomizeModal={this.closeCustomizedTrial}
                />
648
            </Stack>
Lijiao's avatar
Lijiao committed
649
650
651
652
        );
    }
}

653
export default TableList;