Unverified Commit f1105409 authored by liuzhe-lz's avatar liuzhe-lz Committed by GitHub
Browse files

Merge pull request #2959 from microsoft/v1.9

Merge v1.9 back to master
parents 0a6c234a 88a225f8
import React from 'react';
import { Stack, TooltipHost, ProgressIndicator } from '@fluentui/react';
import { EXPERIMENT } from '../../../static/datamodel';
import { CONTROLTYPE } from '../../../static/const';
import { convertDuration } from '../../../static/function';
import { EditExperimentParam } from './EditExperimentParam';
import { ExpDurationContext } from './ExpDurationContext';
import { EditExpeParamContext } from './context';
import '../../../static/style/overview/count.scss';
const itemStyle1: React.CSSProperties = {
width: '62%',
height: 80
};
const itemStyle2: React.CSSProperties = {
width: '63%',
height: 80,
textAlign: 'right'
};
export const ExpDuration = (): any => (
<ExpDurationContext.Consumer>
{(value): React.ReactNode => {
const { maxExecDuration, execDuration, updateOverviewPage } = value;
const tooltip = maxExecDuration - execDuration;
const maxExecDurationStr = convertDuration(maxExecDuration);
const percent = execDuration / maxExecDuration;
return (
<Stack horizontal className='ExpDuration'>
<div style={itemStyle1}>
<TooltipHost content={`${convertDuration(tooltip)} remaining`}>
<ProgressIndicator percentComplete={percent} barHeight={15} />
</TooltipHost>
</div>
<div style={itemStyle2}>
<Stack horizontal></Stack>
<EditExpeParamContext.Provider
value={{
editType: CONTROLTYPE[0],
field: 'maxExecDuration',
title: 'Max duration',
maxExecDuration: maxExecDurationStr,
maxTrialNum: EXPERIMENT.profile.params.maxTrialNum,
trialConcurrency: EXPERIMENT.profile.params.trialConcurrency,
updateOverviewPage
}}
>
<EditExperimentParam />
</EditExpeParamContext.Provider>
</div>
</Stack>
);
}}
</ExpDurationContext.Consumer>
);
import React from 'react';
export const ExpDurationContext = React.createContext({
maxExecDuration: 0,
execDuration: 0,
// eslint-disable-next-line @typescript-eslint/no-empty-function
updateOverviewPage: (): void => {}
});
import * as React from 'react';
import { Stack, TooltipHost, ProgressIndicator } from '@fluentui/react';
import { EXPERIMENT, TRIALS } from '../../../static/datamodel';
import { CONTROLTYPE } from '../../../static/const';
import { EditExperimentParam } from './EditExperimentParam';
import { EditExpeParamContext } from './context';
import { ExpDurationContext } from './ExpDurationContext';
const itemStyles: React.CSSProperties = {
width: '62%'
};
const itemStyle2: React.CSSProperties = {
width: '63%',
textAlign: 'right'
};
const itemStyle1: React.CSSProperties = {
width: '30%',
height: 50
};
const itemRunning: React.CSSProperties = {
width: '42%',
height: 56
};
export const TrialCount = (): any => {
const count = TRIALS.countStatus();
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const stoppedCount = count.get('USER_CANCELED')! + count.get('SYS_CANCELED')! + count.get('EARLY_STOPPED')!;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const bar2 = count.get('RUNNING')! + count.get('SUCCEEDED')! + count.get('FAILED')! + stoppedCount;
// support type [0, 1], not 98%
const bar2Percent = bar2 / EXPERIMENT.profile.params.maxTrialNum;
return (
<ExpDurationContext.Consumer>
{(value): React.ReactNode => {
const { updateOverviewPage } = value;
return (
<React.Fragment>
<Stack horizontal horizontalAlign='space-between' className='ExpDuration'>
<div style={itemStyles}>
<TooltipHost content={bar2.toString()}>
<ProgressIndicator percentComplete={bar2Percent} barHeight={15} />
</TooltipHost>
<Stack horizontal className='mess'>
<div style={itemRunning} className='basic'>
<p>Running</p>
<div>{count.get('RUNNING')}</div>
</div>
<div style={itemStyle1} className='basic'>
<p>Failed</p>
<div>{count.get('FAILED')}</div>
</div>
<div style={itemStyle1} className='basic'>
<p>Stopped</p>
<div>{stoppedCount}</div>
</div>
</Stack>
<Stack horizontal horizontalAlign='space-between' className='mess'>
<div style={itemStyle1} className='basic'>
<p>Succeeded</p>
<div>{count.get('SUCCEEDED')}</div>
</div>
<div style={itemStyle1} className='basic'>
<p>Waiting</p>
<div>{count.get('WAITING')}</div>
</div>
</Stack>
</div>
<div style={itemStyle2}>
<EditExpeParamContext.Provider
value={{
title: 'Max trial numbers',
field: 'maxTrialNum',
editType: CONTROLTYPE[1],
maxExecDuration: '',
maxTrialNum: EXPERIMENT.profile.params.maxTrialNum,
trialConcurrency: EXPERIMENT.profile.params.trialConcurrency,
updateOverviewPage
}}
>
<EditExperimentParam />
</EditExpeParamContext.Provider>
<EditExpeParamContext.Provider
value={{
title: 'Concurrency',
field: 'trialConcurrency',
editType: CONTROLTYPE[2],
// maxExecDuration: EXPERIMENT.profile.params.maxExecDuration,
maxExecDuration: '',
maxTrialNum: EXPERIMENT.profile.params.maxTrialNum,
trialConcurrency: EXPERIMENT.profile.params.trialConcurrency,
updateOverviewPage
}}
>
<EditExperimentParam />
</EditExpeParamContext.Provider>
</div>
</Stack>
</React.Fragment>
);
}}
</ExpDurationContext.Consumer>
);
};
import React from 'react';
/***
* const CONTROLTYPE = ['MAX_EXEC_DURATION', 'MAX_TRIAL_NUM', 'TRIAL_CONCURRENCY', 'SEARCH_SPACE'];
* [0], 'MAX_EXEC_DURATION', params.maxExecDuration
* [1], 'MAX_TRIAL_NUM', params.maxTrialNum
* [2], 'TRIAL_CONCURRENCY', params.trialConcurrency
*/
export const EditExpeParamContext = React.createContext({
editType: '',
field: '',
title: '',
maxExecDuration: '',
maxTrialNum: 0,
trialConcurrency: 0,
// eslint-disable-next-line @typescript-eslint/no-empty-function
updateOverviewPage: (): void => {}
});
import React, { useState, useCallback } from 'react';
import { Stack, Callout, Link, IconButton } from '@fluentui/react';
import LogDrawer from '../../modals/LogPanel';
import { EXPERIMENT } from '../../../static/datamodel';
import { formatTimestamp } from '../../../static/function';
import { useId } from '@uifabric/react-hooks';
import { BestMetricContext } from '../../Overview';
import { styles } from './basicInfoStyles';
import '../../../static/style/progress/progress.scss';
import '../../../static/style/progress/probar.scss';
export const ReBasicInfo = (): any => {
const labelId: string = useId('callout-label');
const descriptionId: string = useId('callout-description');
const ref = React.createRef<HTMLDivElement>();
const [isCalloutVisible, setCalloutVisible] = useState(false);
const [isShowLogDrawer, setShowLogDrawer] = useState(false);
const onDismiss = useCallback(() => setCalloutVisible(false), []);
const showCallout = useCallback(() => setCalloutVisible(true), []);
const closeLogDrawer = useCallback(() => setShowLogDrawer(false), []);
const ShowLogDrawer = useCallback(() => setShowLogDrawer(true), []);
return (
<div>
<div className='basic'>
<p>ID: {EXPERIMENT.profile.id}</p>
<div>{EXPERIMENT.profile.params.experimentName}</div>
</div>
<div className='basic'>
<Stack className='basic'>
<p>Status</p>
<Stack horizontal className='status'>
<span className={`${EXPERIMENT.status} status-text`}>{EXPERIMENT.status}</span>
{EXPERIMENT.status === 'ERROR' ? (
<div>
<div className={styles.buttonArea} ref={ref}>
<IconButton
iconProps={{ iconName: 'info' }}
onClick={isCalloutVisible ? onDismiss : showCallout}
/>
</div>
{isCalloutVisible && (
<Callout
className={styles.callout}
ariaLabelledBy={labelId}
ariaDescribedBy={descriptionId}
role='alertdialog'
gapSpace={0}
target={ref}
onDismiss={onDismiss}
setInitialFocus={true}
>
<div className={styles.header}>
<p className={styles.title} id={labelId}>
Error
</p>
</div>
<div className={styles.inner}>
<p className={styles.subtext} id={descriptionId}>
{EXPERIMENT.error}
</p>
<div className={styles.actions}>
<Link className={styles.link} onClick={ShowLogDrawer}>
Learn about
</Link>
</div>
</div>
</Callout>
)}
</div>
) : null}
</Stack>
</Stack>
</div>
<div className='basic'>
<BestMetricContext.Consumer>
{(value): React.ReactNode => (
<Stack>
<p>Best metric</p>
<div>{isNaN(value.bestAccuracy) ? 'N/A' : value.bestAccuracy.toFixed(6)}</div>
</Stack>
)}
</BestMetricContext.Consumer>
</div>
<div className='basic'>
<p>Start time</p>
<div className='nowrap'>{formatTimestamp(EXPERIMENT.profile.startTime)}</div>
</div>
<div className='basic'>
<p>End time</p>
<div className='nowrap'>{formatTimestamp(EXPERIMENT.profile.endTime)}</div>
</div>
{/* learn about click -> default active key is dispatcher. */}
{isShowLogDrawer ? <LogDrawer closeDrawer={closeLogDrawer} activeTab='dispatcher' /> : null}
</div>
);
};
import React from 'react';
import { TooltipHost, Stack } from '@fluentui/react';
import { EXPERIMENT } from '../../../static/datamodel';
import '../../../static/style/overview/command.scss';
export const Command = (): any => {
const clusterMetaData = EXPERIMENT.profile.params.clusterMetaData;
const tuner = EXPERIMENT.profile.params.tuner;
const advisor = EXPERIMENT.profile.params.advisor;
const assessor = EXPERIMENT.profile.params.assessor;
let title = '';
let builtinName = '';
let trialCommand = 'unknown';
if (tuner !== undefined) {
title = title.concat('Tuner');
if (tuner.builtinTunerName !== undefined) {
builtinName = builtinName.concat(tuner.builtinTunerName);
}
}
if (advisor !== undefined) {
title = title.concat('/ Assessor');
if (advisor.builtinAdvisorName !== undefined) {
builtinName = builtinName.concat(advisor.builtinAdvisorName);
}
}
if (assessor !== undefined) {
title = title.concat('/ Addvisor');
if (assessor.builtinAssessorName !== undefined) {
builtinName = builtinName.concat(assessor.builtinAssessorName);
}
}
if (clusterMetaData !== undefined) {
for (const item of clusterMetaData) {
if (item.key === 'command') {
trialCommand = item.value;
}
}
}
return (
<div className='command basic'>
<div className='command1'>
<p>Training platform</p>
<div className='nowrap'>{EXPERIMENT.profile.params.trainingServicePlatform}</div>
<p className='lineMargin'>{title}</p>
<div className='nowrap'>{builtinName}</div>
</div>
<Stack className='command2'>
<p>Log directory</p>
<div className='nowrap'>
<TooltipHost content={EXPERIMENT.profile.logDir || 'unknown'} className='nowrap'>
{EXPERIMENT.profile.logDir || 'unknown'}
</TooltipHost>
</div>
<p className='lineMargin'>Trial command</p>
<div className='nowrap'>
<TooltipHost content={trialCommand || 'unknown'} className='nowrap'>
{trialCommand || 'unknown'}
</TooltipHost>
</div>
</Stack>
</div>
);
};
import { FontWeights, mergeStyleSets, getTheme } from '@fluentui/react';
const theme = getTheme();
export const styles = mergeStyleSets({
buttonArea: {
verticalAlign: 'top',
display: 'inline-block',
textAlign: 'center',
// margin: '0 100px',
minWidth: 30,
height: 30
},
callout: {
maxWidth: 300
},
header: {
padding: '18px 24px 12px'
},
title: [
theme.fonts.xLarge,
{
margin: 0,
color: theme.palette.neutralPrimary,
fontWeight: FontWeights.semilight
}
],
inner: {
height: '100%',
padding: '0 24px 20px'
},
actions: {
position: 'relative',
marginTop: 20,
width: '100%',
whiteSpace: 'nowrap'
},
subtext: [
theme.fonts.small,
{
margin: 0,
color: theme.palette.neutralPrimary,
fontWeight: FontWeights.semilight
}
],
link: [
theme.fonts.medium,
{
color: theme.palette.neutralPrimary
}
]
});
const itemStyle1: React.CSSProperties = {
width: '75%'
};
const itemStyleSucceed: React.CSSProperties = {
width: '28%'
};
const itemStyle2: React.CSSProperties = {
height: 38
};
// top trials entries
const entriesOption = [
{ key: '10', text: '10' },
{ key: '20', text: '20' },
{ key: '30', text: '30' },
{ key: '50', text: '40' },
{ key: '100', text: '100' }
];
export { itemStyle1, itemStyleSucceed, itemStyle2, entriesOption };
import * as React from 'react'; import * as React from 'react';
import { DetailsRow, IDetailsRowBaseProps } from '@fluentui/react'; import { DetailsRow, IDetailsRowBaseProps } from '@fluentui/react';
import OpenRow from '../public-child/OpenRow'; import OpenRow from '../../public-child/OpenRow';
interface DetailsProps { interface DetailsProps {
detailsProps: IDetailsRowBaseProps; detailsProps: IDetailsRowBaseProps;
......
import * as React from 'react'; import * as React from 'react';
import { DetailsList, IDetailsListProps, IColumn } from '@fluentui/react'; import { DetailsList, IDetailsListProps, IColumn } from '@fluentui/react';
import DefaultMetric from '../public-child/DefaultMetric'; import DefaultMetric from '../../public-child/DefaultMetric';
import Details from './Details'; import Details from './Details';
import { convertDuration } from '../../static/function'; import { convertDuration } from '../../../static/function';
import { TRIALS } from '../../static/datamodel'; import { TRIALS } from '../../../static/datamodel';
import { DETAILTABS } from '../stateless-component/NNItabs'; import { DETAILTABS } from '../../stateless-component/NNItabs';
import '../../static/style/succTable.scss'; import '../../../static/style/succTable.scss';
import '../../static/style/openRow.scss'; import '../../../static/style/tableStatus.css';
import '../../../static/style/openRow.scss';
interface SuccessTableProps { interface SuccessTableProps {
trialIds: string[]; trialIds: string[];
...@@ -15,12 +16,17 @@ interface SuccessTableProps { ...@@ -15,12 +16,17 @@ interface SuccessTableProps {
interface SuccessTableState { interface SuccessTableState {
columns: IColumn[]; columns: IColumn[];
source: Array<any>; source: Array<any>;
innerWidth: number;
} }
class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState> { class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState> {
constructor(props: SuccessTableProps) { constructor(props: SuccessTableProps) {
super(props); super(props);
this.state = { columns: this.columns, source: TRIALS.table(this.props.trialIds) }; this.state = {
columns: this.columns,
source: TRIALS.table(this.props.trialIds),
innerWidth: window.innerWidth
};
} }
private onRenderRow: IDetailsListProps['onRenderRow'] = props => { private onRenderRow: IDetailsListProps['onRenderRow'] = props => {
...@@ -70,8 +76,8 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState> ...@@ -70,8 +76,8 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState>
name: 'Trial No.', name: 'Trial No.',
key: 'sequenceId', key: 'sequenceId',
fieldName: 'sequenceId', // required! fieldName: 'sequenceId', // required!
minWidth: 60, minWidth: (window.innerWidth * 0.333 - 150) / 5,
maxWidth: 120, maxWidth: (window.innerWidth * 0.333 - 150) / 5,
isResizable: true, isResizable: true,
data: 'number', data: 'number',
onColumnClick: this.onColumnClick onColumnClick: this.onColumnClick
...@@ -80,8 +86,8 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState> ...@@ -80,8 +86,8 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState>
name: 'ID', name: 'ID',
key: 'id', key: 'id',
fieldName: 'id', fieldName: 'id',
minWidth: 80, minWidth: (window.innerWidth * 0.333 - 150) / 5,
maxWidth: 100, maxWidth: (window.innerWidth * 0.333 - 150) / 5,
isResizable: true, isResizable: true,
className: 'tableHead leftTitle', className: 'tableHead leftTitle',
data: 'string', data: 'string',
...@@ -90,8 +96,8 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState> ...@@ -90,8 +96,8 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState>
{ {
name: 'Duration', name: 'Duration',
key: 'duration', key: 'duration',
minWidth: 100, minWidth: (window.innerWidth * 0.333 - 150) / 5,
maxWidth: 210, maxWidth: (window.innerWidth * 0.333 - 150) / 5,
isResizable: true, isResizable: true,
fieldName: 'duration', fieldName: 'duration',
data: 'number', data: 'number',
...@@ -105,8 +111,8 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState> ...@@ -105,8 +111,8 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState>
{ {
name: 'Status', name: 'Status',
key: 'status', key: 'status',
minWidth: 140, minWidth: (window.innerWidth * 0.333 - 150) / 5,
maxWidth: 210, maxWidth: (window.innerWidth * 0.333 - 150) / 5,
isResizable: true, isResizable: true,
fieldName: 'status', fieldName: 'status',
onRender: (item: any): React.ReactNode => { onRender: (item: any): React.ReactNode => {
...@@ -117,8 +123,8 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState> ...@@ -117,8 +123,8 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState>
name: 'Default metric', name: 'Default metric',
key: 'accuracy', key: 'accuracy',
fieldName: 'accuracy', fieldName: 'accuracy',
minWidth: 120, minWidth: (window.innerWidth * 0.333 - 200) / 5,
maxWidth: 360, // maxWidth: (window.innerWidth * 0.333 - 150) / 5,
isResizable: true, isResizable: true,
data: 'number', data: 'number',
onColumnClick: this.onColumnClick, onColumnClick: this.onColumnClick,
...@@ -128,6 +134,17 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState> ...@@ -128,6 +134,17 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState>
} }
]; ];
setInnerWidth = (): void => {
this.setState(() => ({ innerWidth: window.innerWidth }));
};
componentDidMount(): void {
window.addEventListener('resize', this.setInnerWidth);
}
componentWillUnmount(): void {
window.removeEventListener('resize', this.setInnerWidth);
}
componentDidUpdate(prevProps: SuccessTableProps): void { componentDidUpdate(prevProps: SuccessTableProps): void {
if (this.props.trialIds !== prevProps.trialIds) { if (this.props.trialIds !== prevProps.trialIds) {
const { trialIds } = this.props; const { trialIds } = this.props;
...@@ -138,7 +155,6 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState> ...@@ -138,7 +155,6 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState>
render(): React.ReactNode { render(): React.ReactNode {
const { columns, source } = this.state; const { columns, source } = this.state;
const isNoneData = source.length === 0 ? true : false; const isNoneData = source.length === 0 ? true : false;
return ( return (
<div id='succTable'> <div id='succTable'>
<DetailsList <DetailsList
......
import * as React from 'react';
import { DetailsRow, IDetailsRowBaseProps } from '@fluentui/react';
import OpenRow from '../public-child/OpenRow';
interface ExpandableDetailsProps {
detailsProps: IDetailsRowBaseProps;
isExpand: boolean;
}
class ExpandableDetails extends React.Component<ExpandableDetailsProps, {}> {
render(): React.ReactNode {
const { detailsProps, isExpand } = this.props;
return (
<div>
<DetailsRow {...detailsProps} />
{isExpand && <OpenRow trialId={detailsProps.item.id} />}
</div>
);
}
}
export default ExpandableDetails;
...@@ -8,7 +8,7 @@ import JSONTree from 'react-json-tree'; ...@@ -8,7 +8,7 @@ import JSONTree from 'react-json-tree';
import PaiTrialLog from '../public-child/PaiTrialLog'; import PaiTrialLog from '../public-child/PaiTrialLog';
import TrialLog from '../public-child/TrialLog'; import TrialLog from '../public-child/TrialLog';
import MessageInfo from '../modals/MessageInfo'; import MessageInfo from '../modals/MessageInfo';
import '../../static/style/overview.scss'; import '../../static/style/overview/overview.scss';
import '../../static/style/copyParameter.scss'; import '../../static/style/copyParameter.scss';
import '../../static/style/openRow.scss'; import '../../static/style/openRow.scss';
......
import * as React from 'react';
import { DetailsList, Dropdown, Icon, IDetailsListProps, IDropdownOption, IStackTokens, Stack } from '@fluentui/react';
import ReactPaginate from 'react-paginate';
interface PaginationTableState {
itemsPerPage: number;
currentPage: number;
itemsOnPage: any[]; // this needs to be stored in state to prevent re-rendering
}
const horizontalGapStackTokens: IStackTokens = {
childrenGap: 20,
padding: 10
};
function _currentTableOffset(perPage: number, currentPage: number, source: any[]): number {
return perPage === -1 ? 0 : Math.min(currentPage, Math.floor((source.length - 1) / perPage)) * perPage;
}
function _obtainPaginationSlice(perPage: number, currentPage: number, source: any[]): any[] {
if (perPage === -1) {
return source;
} else {
const offset = _currentTableOffset(perPage, currentPage, source);
return source.slice(offset, offset + perPage);
}
}
class PaginationTable extends React.PureComponent<IDetailsListProps, PaginationTableState> {
constructor(props: IDetailsListProps) {
super(props);
this.state = {
itemsPerPage: 20,
currentPage: 0,
itemsOnPage: []
};
}
private _onItemsPerPageSelect(event: React.FormEvent<HTMLDivElement>, item: IDropdownOption | undefined): void {
if (item !== undefined) {
const { items } = this.props;
// use current offset to calculate the next `current_page`
const currentOffset = _currentTableOffset(this.state.itemsPerPage, this.state.currentPage, items);
const itemsPerPage = item.key as number;
const currentPage = Math.floor(currentOffset / itemsPerPage);
this.setState({
itemsPerPage: itemsPerPage,
currentPage: currentPage,
itemsOnPage: _obtainPaginationSlice(itemsPerPage, currentPage, this.props.items)
});
}
}
private _onPageSelect(event: any): void {
const currentPage = event.selected;
this.setState({
currentPage: currentPage,
itemsOnPage: _obtainPaginationSlice(this.state.itemsPerPage, currentPage, this.props.items)
});
}
componentDidUpdate(prevProps: IDetailsListProps): void {
if (prevProps.items !== this.props.items) {
this.setState({
itemsOnPage: _obtainPaginationSlice(this.state.itemsPerPage, this.state.currentPage, this.props.items)
});
}
}
render(): React.ReactNode {
const { itemsPerPage, itemsOnPage } = this.state;
const detailListProps = {
...this.props,
items: itemsOnPage
};
const itemsCount = this.props.items.length;
const pageCount = itemsPerPage === -1 ? 1 : Math.ceil(itemsCount / itemsPerPage);
const perPageOptions = [
{ key: 10, text: '10 items per page' },
{ key: 20, text: '20 items per page' },
{ key: 50, text: '50 items per page' },
{ key: -1, text: 'All items' }
];
return (
<div>
<DetailsList {...detailListProps} />
<Stack
horizontal
horizontalAlign='end'
verticalAlign='baseline'
styles={{ root: { padding: 10 } }}
tokens={horizontalGapStackTokens}
>
<Dropdown
selectedKey={itemsPerPage}
options={perPageOptions}
onChange={this._onItemsPerPageSelect.bind(this)}
styles={{ dropdown: { width: 150 } }}
/>
<ReactPaginate
previousLabel={<Icon aria-hidden={true} iconName='ChevronLeft' />}
nextLabel={<Icon aria-hidden={true} iconName='ChevronRight' />}
breakLabel={'...'}
breakClassName={'break'}
pageCount={pageCount}
marginPagesDisplayed={2}
pageRangeDisplayed={2}
onPageChange={this._onPageSelect.bind(this)}
containerClassName={itemsCount === 0 ? 'pagination hidden' : 'pagination'}
subContainerClassName={'pages pagination'}
disableInitialCallback={false}
activeClassName={'active'}
/>
</Stack>
</div>
);
}
}
export default PaginationTable;
import React, { useState, useCallback } from 'react';
import { DefaultButton, Stack } from '@fluentui/react';
import TrialConfigPanel from './TrialConfigPanel';
import '../../../static/style/overview/config.scss';
export const TrialConfigButton = (): any => {
const [isShowConfigPanel, setShowConfigPanle] = useState(false);
const [activeTab, setActiveTab] = useState('1');
const hideConfigPanel = useCallback(() => setShowConfigPanle(false), []);
const showTrialConfigpPanel = useCallback(() => {
setShowConfigPanle(true);
setActiveTab('config');
}, []);
const showSearchSpacePanel = useCallback(() => {
setShowConfigPanle(true);
setActiveTab('search space');
}, []);
return (
<React.Fragment>
<Stack className='config'>
<DefaultButton text='Config' onClick={showTrialConfigpPanel} />
<DefaultButton text='Search space' onClick={showSearchSpacePanel} />
</Stack>
{isShowConfigPanel && <TrialConfigPanel hideConfigPanel={hideConfigPanel} activeTab={activeTab} />}
</React.Fragment>
);
};
import * as React from 'react';
import { Stack, Panel, Pivot, PivotItem, PrimaryButton } from '@fluentui/react';
import { EXPERIMENT } from '../../../static/datamodel';
import MonacoEditor from 'react-monaco-editor';
import { MONACO } from '../../../static/const';
import { convertDuration } from '../../../static/function';
import { prettyStringify } from '../../../static/json_util';
import lodash from 'lodash';
import '../../../static/style/logDrawer.scss';
interface LogDrawerProps {
hideConfigPanel: () => void;
activeTab?: string;
}
interface LogDrawerState {
panelInnerHeight: number;
}
class TrialConfigPanel extends React.Component<LogDrawerProps, LogDrawerState> {
constructor(props: LogDrawerProps) {
super(props);
this.state = {
panelInnerHeight: window.innerHeight
};
}
setLogDrawerHeight(): void {
this.setState(() => ({ panelInnerHeight: window.innerHeight }));
}
async componentDidMount(): Promise<void> {
window.addEventListener('resize', this.setLogDrawerHeight);
}
componentWillUnmount(): void {
window.removeEventListener('resize', this.setLogDrawerHeight);
}
render(): React.ReactNode {
const { hideConfigPanel, activeTab } = this.props;
const { panelInnerHeight } = this.state;
// [marginTop 16px] + [Search space 46px] +
// button[height: 32px, marginTop: 45px, marginBottom: 25px] + [padding-bottom: 20px]
const monacoEditorHeight = panelInnerHeight - 184;
const blacklist = [
'id',
'logDir',
'startTime',
'endTime',
'experimentName',
'searchSpace',
'trainingServicePlatform'
];
const filter = (key: string, val: any): any => {
return blacklist.includes(key) ? undefined : val;
};
const profile = lodash.cloneDeep(EXPERIMENT.profile);
profile.execDuration = convertDuration(profile.execDuration);
profile.params.maxExecDuration = convertDuration(profile.params.maxExecDuration);
const showProfile = JSON.stringify(profile, filter, 2);
return (
<Stack>
<Panel
isOpen={true}
hasCloseButton={false}
isFooterAtBottom={true}
isLightDismiss={true}
onLightDismissClick={hideConfigPanel}
>
<div className='log-tab-body'>
<Pivot initialSelectedKey={activeTab} style={{ minHeight: 190, paddingTop: '16px' }}>
<PivotItem headerText='Search space' itemKey='search space'>
<MonacoEditor
height={monacoEditorHeight}
language='json'
theme='vs-light'
value={prettyStringify(EXPERIMENT.searchSpace, 300, 2)}
options={MONACO}
/>
</PivotItem>
<PivotItem headerText='Config' itemKey='config'>
<div className='profile'>
<MonacoEditor
width='100%'
height={monacoEditorHeight}
language='json'
theme='vs-light'
value={showProfile}
options={MONACO}
/>
</div>
</PivotItem>
</Pivot>
</div>
<PrimaryButton text='Close' className='configClose' onClick={hideConfigPanel} />
</Panel>
</Stack>
);
}
}
export default TrialConfigPanel;
...@@ -15,7 +15,7 @@ const DETAILTABS = ( ...@@ -15,7 +15,7 @@ const DETAILTABS = (
const NNILOGO = ( const NNILOGO = (
<NavLink to={'/oview'}> <NavLink to={'/oview'}>
<img src={require('../../static/img/logo2.png')} alt='NNI logo' style={{ height: 40 }} /> <img src={require('../../static/img/logo.png')} alt='NNI logo' style={{ height: 40 }} />
</NavLink> </NavLink>
); );
......
import * as d3 from 'd3'; import * as d3 from 'd3';
import { Dropdown, IDropdownOption, Stack } from '@fluentui/react'; import { Dropdown, IDropdownOption, Stack, DefaultButton } from '@fluentui/react';
import ParCoords from 'parcoord-es'; import ParCoords from 'parcoord-es';
import 'parcoord-es/dist/parcoords.css'; import 'parcoord-es/dist/parcoords.css';
import * as React from 'react'; import * as React from 'react';
...@@ -9,12 +9,16 @@ import { filterByStatus } from '../../static/function'; ...@@ -9,12 +9,16 @@ import { filterByStatus } from '../../static/function';
import { TableObj, SingleAxis, MultipleAxes } from '../../static/interface'; import { TableObj, SingleAxis, MultipleAxes } from '../../static/interface';
import '../../static/style/button.scss'; import '../../static/style/button.scss';
import '../../static/style/para.scss'; import '../../static/style/para.scss';
import ChangeColumnComponent from '../modals/ChangeColumnComponent';
interface ParaState { interface ParaState {
dimName: string[]; dimName: string[];
selectedPercent: string; selectedPercent: string;
primaryMetricKey: string; primaryMetricKey: string;
noChart: boolean; noChart: boolean;
customizeColumnsDialogVisible: boolean;
availableDimensions: string[];
chosenDimensions: string[];
} }
interface ParaProps { interface ParaProps {
...@@ -45,7 +49,10 @@ class Para extends React.Component<ParaProps, ParaState> { ...@@ -45,7 +49,10 @@ class Para extends React.Component<ParaProps, ParaState> {
dimName: [], dimName: [],
primaryMetricKey: 'default', primaryMetricKey: 'default',
selectedPercent: '1', selectedPercent: '1',
noChart: true noChart: true,
customizeColumnsDialogVisible: false,
availableDimensions: [],
chosenDimensions: []
}; };
} }
...@@ -82,11 +89,24 @@ class Para extends React.Component<ParaProps, ParaState> { ...@@ -82,11 +89,24 @@ class Para extends React.Component<ParaProps, ParaState> {
} }
render(): React.ReactNode { render(): React.ReactNode {
const { selectedPercent, noChart } = this.state; const {
selectedPercent,
noChart,
customizeColumnsDialogVisible,
availableDimensions,
chosenDimensions
} = this.state;
return ( return (
<div className='parameter'> <div className='parameter'>
<Stack horizontal className='para-filter' horizontalAlign='end'> <Stack horizontal className='para-filter' horizontalAlign='end'>
<DefaultButton
text='Add/Remove axes'
onClick={(): void => {
this.setState({ customizeColumnsDialogVisible: true });
}}
styles={{ root: { marginRight: 10 } }}
/>
<Dropdown <Dropdown
selectedKey={selectedPercent} selectedKey={selectedPercent}
onChange={this.percentNum} onChange={this.percentNum}
...@@ -101,6 +121,21 @@ class Para extends React.Component<ParaProps, ParaState> { ...@@ -101,6 +121,21 @@ class Para extends React.Component<ParaProps, ParaState> {
/> />
{this.finalKeysDropdown()} {this.finalKeysDropdown()}
</Stack> </Stack>
{customizeColumnsDialogVisible && availableDimensions.length > 0 && (
<ChangeColumnComponent
selectedColumns={chosenDimensions}
allColumns={availableDimensions.map(dim => ({ key: dim, name: dim }))}
onSelectedChange={(selected: string[]): void => {
this.setState({ chosenDimensions: selected }, () => {
this.renderParallelCoordinates();
});
}}
onHideDialog={(): void => {
this.setState({ customizeColumnsDialogVisible: false });
}}
minSelected={2}
/>
)}
<div className='parcoords' style={this.chartMulineStyle} ref={this.paraRef} /> <div className='parcoords' style={this.chartMulineStyle} ref={this.paraRef} />
{noChart && <div className='nodata'>No data</div>} {noChart && <div className='nodata'>No data</div>}
</div> </div>
...@@ -143,13 +178,13 @@ class Para extends React.Component<ParaProps, ParaState> { ...@@ -143,13 +178,13 @@ class Para extends React.Component<ParaProps, ParaState> {
private renderParallelCoordinates(): void { private renderParallelCoordinates(): void {
const { searchSpace } = this.props; const { searchSpace } = this.props;
const percent = parseFloat(this.state.selectedPercent); const percent = parseFloat(this.state.selectedPercent);
const { primaryMetricKey } = this.state; const { primaryMetricKey, chosenDimensions } = this.state;
const inferredSearchSpace = TRIALS.inferredSearchSpace(searchSpace); const inferredSearchSpace = TRIALS.inferredSearchSpace(searchSpace);
const inferredMetricSpace = TRIALS.inferredMetricSpace(); const inferredMetricSpace = TRIALS.inferredMetricSpace();
let convertedTrials = this.getTrialsAsObjectList(inferredSearchSpace, inferredMetricSpace); let convertedTrials = this.getTrialsAsObjectList(inferredSearchSpace, inferredMetricSpace);
const dimensions: [any, any][] = []; const dimensions: [string, any][] = [];
let colorDim: string | undefined = undefined, let colorDim: string | undefined = undefined,
colorScale: any = undefined; colorScale: any = undefined;
// treat every axis as numeric to fit for brush // treat every axis as numeric to fit for brush
...@@ -213,7 +248,11 @@ class Para extends React.Component<ParaProps, ParaState> { ...@@ -213,7 +248,11 @@ class Para extends React.Component<ParaProps, ParaState> {
} }
this.pcs this.pcs
.data(convertedTrials) .data(convertedTrials)
.dimensions(dimensions.reduce((obj, entry) => ({ ...obj, [entry[0]]: entry[1] }), {})); .dimensions(
dimensions
.filter(([d, _]) => chosenDimensions.length === 0 || chosenDimensions.includes(d))
.reduce((obj, entry) => ({ ...obj, [entry[0]]: entry[1] }), {})
);
if (firstRun) { if (firstRun) {
this.pcs this.pcs
.margin(this.innerChartMargins) .margin(this.innerChartMargins)
...@@ -230,6 +269,12 @@ class Para extends React.Component<ParaProps, ParaState> { ...@@ -230,6 +269,12 @@ class Para extends React.Component<ParaProps, ParaState> {
if (firstRun) { if (firstRun) {
this.setState({ noChart: false }); this.setState({ noChart: false });
} }
// set new available dims
this.setState({
availableDimensions: dimensions.map(e => e[0]),
chosenDimensions: chosenDimensions.length === 0 ? dimensions.map(e => e[0]) : chosenDimensions
});
} }
private getTrialsAsObjectList(inferredSearchSpace: MultipleAxes, inferredMetricSpace: MultipleAxes): {}[] { private getTrialsAsObjectList(inferredSearchSpace: MultipleAxes, inferredMetricSpace: MultipleAxes): {}[] {
......
import React, { lazy } from 'react';
import axios from 'axios';
import ReactEcharts from 'echarts-for-react';
import { import {
Stack, DefaultButton,
Dropdown, Dropdown,
DetailsList,
IDetailsListProps,
DetailsListLayoutMode,
PrimaryButton,
Modal,
IDropdownOption,
IColumn, IColumn,
Icon,
IDropdownOption,
PrimaryButton,
Selection, Selection,
SelectionMode, SelectionMode,
IconButton, Stack,
TooltipHost, StackItem,
IStackTokens TooltipHost
} from '@fluentui/react'; } from '@fluentui/react';
import ReactPaginate from 'react-paginate'; import React from 'react';
import { LineChart, blocked, copy } from '../buttons/Icon';
import { MANAGER_IP, COLUMNPro } from '../../static/const';
import { convertDuration, formatTimestamp, intermediateGraphOption, parseMetrics } from '../../static/function';
import { EXPERIMENT, TRIALS } from '../../static/datamodel'; import { EXPERIMENT, TRIALS } from '../../static/datamodel';
import { TableRecord, TrialJobInfo } from '../../static/interface'; import { convertDuration, formatTimestamp } from '../../static/function';
const Details = lazy(() => import('../overview/Details')); import { TableObj } from '../../static/interface';
const ChangeColumnComponent = lazy(() => import('../modals/ChangeColumnComponent'));
const Compare = lazy(() => import('../modals/Compare'));
const KillJob = lazy(() => import('../modals/Killjob'));
const Customize = lazy(() => import('../modals/CustomizedTrial'));
import { contentStyles, iconButtonStyles } from '../buttons/ModalTheme';
import '../../static/style/search.scss'; import '../../static/style/search.scss';
import '../../static/style/tableStatus.css'; import '../../static/style/tableStatus.css';
import '../../static/style/logPath.scss'; import '../../static/style/logPath.scss';
import '../../static/style/table.scss'; import '../../static/style/table.scss';
import '../../static/style/button.scss'; import '../../static/style/button.scss';
import '../../static/style/logPath.scss';
import '../../static/style/openRow.scss'; import '../../static/style/openRow.scss';
import '../../static/style/pagination.scss'; import '../../static/style/pagination.scss';
import '../../static/style/search.scss';
import '../../static/style/table.scss';
import '../../static/style/tableStatus.css';
import { blocked, copy, LineChart, tableListIcon } from '../buttons/Icon';
import ChangeColumnComponent from '../modals/ChangeColumnComponent';
import Compare from '../modals/Compare';
import Customize from '../modals/CustomizedTrial';
import KillJob from '../modals/Killjob';
import ExpandableDetails from '../public-child/ExpandableDetails';
import PaginationTable from '../public-child/PaginationTable';
import { Trial } from '../../static/model/trial';
const echarts = require('echarts/lib/echarts'); const echarts = require('echarts/lib/echarts');
require('echarts/lib/chart/line'); require('echarts/lib/chart/line');
...@@ -45,749 +43,518 @@ echarts.registerTheme('my_theme', { ...@@ -45,749 +43,518 @@ echarts.registerTheme('my_theme', {
color: '#3c8dbc' color: '#3c8dbc'
}); });
const horizontalGapStackTokens: IStackTokens = { type SearchOptionType = 'id' | 'trialnum' | 'status' | 'parameters';
childrenGap: 20, const searchOptionLiterals = {
padding: 10 id: 'ID',
trialnum: 'Trial No.',
status: 'Status',
parameters: 'Parameters'
}; };
interface TableListProps { const defaultDisplayedColumns = ['sequenceId', 'id', 'duration', 'status', 'latestAccuracy'];
pageSize: number;
tableSource: Array<TableRecord>;
columnList: string[]; // user select columnKeys
changeColumn: (val: string[]) => void;
trialsUpdateBroadcast: number;
}
interface SortInfo { interface SortInfo {
field: string; field: string;
isDescend?: boolean; isDescend?: boolean;
} }
function _copyAndSort<T>(items: T[], columnKey: string, isSortedDescending?: boolean): any {
const key = columnKey as keyof T;
return items.slice(0).sort(function(a: T, b: T): any {
if (
a[key] === undefined ||
Object.is(a[key], NaN) ||
Object.is(a[key], Infinity) ||
Object.is(a[key], -Infinity) ||
typeof a[key] === 'object'
) {
return 1;
}
if (
b[key] === undefined ||
Object.is(b[key], NaN) ||
Object.is(b[key], Infinity) ||
Object.is(b[key], -Infinity) ||
typeof b[key] === 'object'
) {
return -1;
}
return (isSortedDescending ? a[key] < b[key] : a[key] > b[key]) ? 1 : -1;
});
}
function _inferColumnTitle(columnKey: string): string {
if (columnKey === 'sequenceId') {
return 'Trial No.';
} else if (columnKey === 'id') {
return 'ID';
} else if (columnKey === 'intermediateCount') {
return 'Intermediate results (#)';
} else if (columnKey.startsWith('space/')) {
return columnKey.split('/', 2)[1] + ' (space)';
} else if (columnKey === 'latestAccuracy') {
return 'Default metric'; // to align with the original design
} else if (columnKey.startsWith('metric/')) {
return columnKey.split('/', 2)[1] + ' (metric)';
} else if (columnKey.startsWith('_')) {
return columnKey;
} else {
// camel case to verbose form
const withSpace = columnKey.replace(/[A-Z]/g, letter => ` ${letter.toLowerCase()}`);
return withSpace.charAt(0).toUpperCase() + withSpace.slice(1);
}
}
interface TableListProps {
tableSource: TableObj[];
trialsUpdateBroadcast: number;
}
interface TableListState { interface TableListState {
intermediateOption: object; displayedItems: any[];
modalVisible: boolean; displayedColumns: string[];
isObjFinal: boolean; columns: IColumn[];
isShowColumn: boolean; searchType: SearchOptionType;
selectRows: Array<any>; searchText: string;
isShowCompareModal: boolean; selectedRowIds: string[];
selectedRowKeys: string[] | number[]; customizeColumnsDialogVisible: boolean;
intermediateData: Array<object>; // a trial's intermediate results (include dict) compareDialogVisible: boolean;
intermediateId: string; intermediateDialogTrial: TableObj | undefined;
intermediateOtherKeys: string[]; copiedTrialId: string | undefined;
isShowCustomizedModal: boolean; sortInfo: SortInfo;
copyTrialId: string; // user copy trial to submit a new customized trial
isCalloutVisible: boolean; // kill job button callout [kill or not kill job window]
intermediateKey: string; // intermeidate modal: which key is choosed.
isExpand: boolean;
modalIntermediateWidth: number;
modalIntermediateHeight: number;
tableColumns: IColumn[];
allColumnList: string[];
tableSourceForSort: Array<TableRecord>;
sortMessage: SortInfo;
offset: number;
tablePerPage: Array<TableRecord>;
perPage: number;
currentPage: number;
pageCount: number;
} }
class TableList extends React.Component<TableListProps, TableListState> { class TableList extends React.Component<TableListProps, TableListState> {
public intervalTrialLog = 10; private _selection: Selection;
public trialId!: string; private _expandedTrialIds: Set<string>;
constructor(props: TableListProps) { constructor(props: TableListProps) {
super(props); super(props);
this.state = { this.state = {
intermediateOption: {}, displayedItems: [],
modalVisible: false, displayedColumns: defaultDisplayedColumns,
isObjFinal: false, columns: [],
isShowColumn: false, searchType: 'id',
isShowCompareModal: false, searchText: '',
selectRows: [], customizeColumnsDialogVisible: false,
selectedRowKeys: [], // close selected trial message after modal closed compareDialogVisible: false,
intermediateData: [], selectedRowIds: [],
intermediateId: '', intermediateDialogTrial: undefined,
intermediateOtherKeys: [], copiedTrialId: undefined,
isShowCustomizedModal: false, sortInfo: { field: '', isDescend: true }
isCalloutVisible: false,
copyTrialId: '',
intermediateKey: 'default',
isExpand: false,
modalIntermediateWidth: window.innerWidth,
modalIntermediateHeight: window.innerHeight,
tableColumns: this.initTableColumnList(this.props.columnList),
allColumnList: this.getAllColumnKeys(),
sortMessage: { field: '', isDescend: false },
offset: 0,
tablePerPage: [],
perPage: 20,
currentPage: 0,
pageCount: 0,
tableSourceForSort: this.props.tableSource
}; };
}
// sort for table column this._selection = new Selection({
onColumnClick = (ev: React.MouseEvent<HTMLElement>, getColumn: IColumn): void => { onSelectionChanged: (): void => {
const { tableColumns } = this.state; this.setState({
const newColumns: IColumn[] = tableColumns.slice(); selectedRowIds: this._selection.getSelection().map(s => (s as any).id)
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;
} }
}); });
this.setState( this._expandedTrialIds = new Set<string>();
{ }
tableColumns: newColumns,
sortMessage: { field: getColumn.key, isDescend: currColumn.isSortedDescending }
},
() => {
this.updateData();
}
);
};
AccuracyColumnConfig: any = {
name: 'Default metric',
className: 'leftTitle',
key: 'latestAccuracy',
fieldName: 'latestAccuracy',
minWidth: 200,
maxWidth: 300,
isResizable: true,
data: 'number',
onColumnClick: this.onColumnClick,
onRender: (item): React.ReactNode => (
<TooltipHost content={item.formattedLatestAccuracy}>
<div className='ellipsis'>{item.formattedLatestAccuracy}</div>
</TooltipHost>
)
};
SequenceIdColumnConfig: any = {
name: 'Trial No.',
key: 'sequenceId',
fieldName: 'sequenceId',
minWidth: 80,
maxWidth: 240,
className: 'tableHead',
data: 'number',
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,
maxWidth: 400,
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',
minWidth: 200,
maxWidth: 400,
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,
maxWidth: 300,
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,
maxWidth: 250,
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 (record: TrialJobInfo, event: React.SyntheticEvent<EventTarget>): Promise<void> => { /* Search related methods */
event.preventDefault();
event.stopPropagation(); // This functions as the filter for the final trials displayed in the current table
const res = await axios.get(`${MANAGER_IP}/metric-data/${record.id}`); private _filterTrials(trials: TableObj[]): TableObj[] {
if (res.status === 200) { const { searchText, searchType } = this.state;
const intermediateArr: number[] = []; // search a trial by Trial No. | Trial ID | Parameters | Status
// support intermediate result is dict because the last intermediate result is let searchFilter = (_: TableObj): boolean => true; // eslint-disable-line no-unused-vars
// final result in a succeed trial, it may be a dict. if (searchText.trim()) {
// get intermediate result dict keys array if (searchType === 'id') {
const { intermediateKey } = this.state; searchFilter = (trial): boolean => trial.id.toUpperCase().includes(searchText.toUpperCase());
const otherkeys: string[] = []; } else if (searchType === 'trialnum') {
const metricDatas = res.data; searchFilter = (trial): boolean => trial.sequenceId.toString() === searchText;
if (metricDatas.length !== 0) { } else if (searchType === 'status') {
// just add type=number keys searchFilter = (trial): boolean => trial.status.toUpperCase().includes(searchText.toUpperCase());
const intermediateMetrics = parseMetrics(metricDatas[0].data); } else if (searchType === 'parameters') {
for (const key in intermediateMetrics) { // TODO: support filters like `x: 2` (instead of `'x': 2`)
if (typeof intermediateMetrics[key] === 'number') { searchFilter = (trial): boolean => JSON.stringify(trial.description.parameters).includes(searchText);
otherkeys.push(key);
}
}
} }
// intermediateArr just store default val
metricDatas.map(item => {
if (item.type === 'PERIODICAL') {
const temp = parseMetrics(item.data);
if (typeof temp === 'object') {
intermediateArr.push(temp[intermediateKey]);
} else {
intermediateArr.push(temp);
}
}
});
const intermediate = intermediateGraphOption(intermediateArr, record.id);
this.setState({
intermediateData: res.data, // store origin intermediate data for a trial
intermediateOption: intermediate,
intermediateOtherKeys: otherkeys,
intermediateId: record.id
});
} }
this.setState({ modalVisible: true }); return trials.filter(searchFilter);
}; }
// intermediate button click -> intermediate graph for each trial private _updateSearchFilterType(_event: React.FormEvent<HTMLDivElement>, item: IDropdownOption | undefined): void {
// support intermediate is dict
selectOtherKeys = (event: React.FormEvent<HTMLDivElement>, item?: IDropdownOption): void => {
if (item !== undefined) { if (item !== undefined) {
const value = item.text; const value = item.key.toString();
const isShowDefault: boolean = value === 'default' ? true : false; if (searchOptionLiterals.hasOwnProperty(value)) {
const { intermediateData, intermediateId } = this.state; this.setState({ searchType: value as SearchOptionType }, this._updateTableSource);
const intermediateArr: number[] = [];
// just watch default key-val
if (isShowDefault === true) {
Object.keys(intermediateData).map(item => {
if (intermediateData[item].type === 'PERIODICAL') {
const temp = parseMetrics(intermediateData[item].data);
if (typeof temp === 'object') {
intermediateArr.push(temp[value]);
} else {
intermediateArr.push(temp);
}
}
});
} 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({
intermediateKey: value,
intermediateOption: intermediate
});
} }
}; }
hideIntermediateModal = (): void => {
this.setState({
modalVisible: false
});
};
hideShowColumnModal = (): void => {
this.setState(() => ({ isShowColumn: false }));
};
// click add column btn, just show the modal of addcolumn
addColumn = (): void => {
// show user select check button
this.setState(() => ({ isShowColumn: true }));
};
fillSelectedRowsTostate = (selected: number[] | string[], selectedRows: Array<TableRecord>): void => {
this.setState({ selectRows: selectedRows, selectedRowKeys: selected });
};
// open Compare-modal private _updateSearchText(ev: React.ChangeEvent<HTMLInputElement>): void {
compareBtn = (): void => { this.setState({ searchText: ev.target.value }, this._updateTableSource);
const { selectRows } = this.state; }
if (selectRows.length === 0) {
alert('Please select datas you want to compare!');
} else {
this.setState({ isShowCompareModal: true });
}
};
// close Compare-modal /* Table basic function related methods */
hideCompareModal = (): void => {
// close modal. clear select rows data, clear selected track
this.setState({ isShowCompareModal: false, selectedRowKeys: [], selectRows: [] });
};
// open customized trial modal private _onColumnClick(ev: React.MouseEvent<HTMLElement>, column: IColumn): void {
private setCustomizedTrial = (trialId: string, event: React.SyntheticEvent<EventTarget>): void => { // handle the click events on table header (do sorting)
event.preventDefault(); const { columns } = this.state;
event.stopPropagation(); const newColumns: IColumn[] = columns.slice();
this.setState({ const currColumn: IColumn = newColumns.filter(currCol => column.key === currCol.key)[0];
isShowCustomizedModal: true, const isSortedDescending = !currColumn.isSortedDescending;
copyTrialId: trialId this.setState(
}); {
}; sortInfo: { field: column.key, isDescend: isSortedDescending }
},
this._updateTableSource
);
}
private closeCustomizedTrial = (): void => { private _trialsToTableItems(trials: TableObj[]): any[] {
this.setState({ // TODO: use search space and metrics space from TRIALS will cause update issues.
isShowCustomizedModal: false, const searchSpace = TRIALS.inferredSearchSpace(EXPERIMENT.searchSpaceNew);
copyTrialId: '' const metricSpace = TRIALS.inferredMetricSpace();
const items = trials.map(trial => {
const ret = {
sequenceId: trial.sequenceId,
id: trial.id,
startTime: (trial as Trial).info.startTime, // FIXME: why do we need info here?
endTime: (trial as Trial).info.endTime,
duration: trial.duration,
status: trial.status,
intermediateCount: trial.intermediates.length,
_expandDetails: this._expandedTrialIds.has(trial.id) // hidden field names should start with `_`
};
for (const [k, v] of trial.parameters(searchSpace)) {
ret[`space/${k.baseName}`] = v;
}
for (const [k, v] of trial.metrics(metricSpace)) {
ret[`metric/${k.baseName}`] = v;
}
ret['latestAccuracy'] = (trial as Trial).latestAccuracy;
ret['_formattedLatestAccuracy'] = (trial as Trial).formatLatestAccuracy();
return ret;
}); });
};
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({ const { sortInfo } = this.state;
onSelectionChanged: (): void => { if (sortInfo.field !== '') {
this.setState(() => ({ selectRows: this.getSelectedRows.getSelection() })); return _copyAndSort(items, sortInfo.field, sortInfo.isDescend);
} } else {
}); return items;
// trial parameters & dict final keys & Trial No. Id ...
private getAllColumnKeys = (): string[] => {
const tableSource: Array<TableRecord> = JSON.parse(JSON.stringify(this.props.tableSource));
// parameter as table column
const parameterStr: string[] = [];
if (!EXPERIMENT.isNestedExp()) {
if (tableSource.length > 0) {
const trialMess = TRIALS.getTrial(tableSource[0].id);
const trial = trialMess.description.parameters;
const parameterColumn: string[] = Object.keys(trial);
parameterColumn.forEach(value => {
parameterStr.push(`${value} (search space)`);
});
}
} }
// concat trial all final keys and remove dup "default" val, return list }
const finalKeysList = TRIALS.finalKeys().filter(item => item !== 'default');
return COLUMNPro.concat(parameterStr).concat(finalKeysList);
};
// get IColumn[] private _buildColumnsFromTableItems(tableItems: any[]): IColumn[] {
// when user click [Add Column] need to use the function // extra column, for a icon to expand the trial details panel
private initTableColumnList = (columnList: string[]): IColumn[] => { const columns: IColumn[] = [
// const { columnList } = this.props; {
const disabledAddCustomizedTrial = ['DONE', 'ERROR', 'STOPPED'].includes(EXPERIMENT.status); key: '_expand',
const showColumn: IColumn[] = []; name: '',
for (const item of columnList) { onRender: (item, index): any => {
const paraColumn = item.match(/ \(search space\)$/); return (
let result; <Icon
if (paraColumn !== null) { aria-hidden={true}
result = paraColumn.input; iconName='ChevronRight'
styles={{
root: {
transition: 'all 0.2s',
transform: `rotate(${item._expandDetails ? 90 : 0}deg)`
}
}}
onClick={(event): void => {
event.stopPropagation();
const newItem: any = { ...item, _expandDetails: !item._expandDetails };
if (newItem._expandDetails) {
// preserve to be restored when refreshed
this._expandedTrialIds.add(newItem.id);
} else {
this._expandedTrialIds.delete(newItem.id);
}
const newItems = [...this.state.displayedItems];
newItems[index as number] = newItem;
this.setState({
displayedItems: newItems
});
}}
onMouseDown={(e): void => {
e.stopPropagation();
}}
onMouseUp={(e): void => {
e.stopPropagation();
}}
/>
);
},
fieldName: 'expand',
isResizable: false,
minWidth: 20,
maxWidth: 20
} }
switch (item) { ];
case 'Trial No.': // looking at the first row only for now
showColumn.push(this.SequenceIdColumnConfig); for (const k of Object.keys(tableItems[0])) {
break; if (k === 'metric/default') {
case 'ID': // FIXME: default metric is hacked as latestAccuracy currently
showColumn.push(this.IdColumnConfig); continue;
break;
case 'Start time':
showColumn.push(this.StartTimeColumnConfig);
break;
case 'End time':
showColumn.push(this.EndTimeColumnConfig);
break;
case 'Duration':
showColumn.push(this.DurationColumnConfig);
break;
case 'Status':
showColumn.push(this.StatusColumnConfig);
break;
case 'Intermediate result':
showColumn.push(this.IntermediateCountColumnConfig);
break;
case 'Default':
showColumn.push(this.AccuracyColumnConfig);
break;
case 'Operation':
showColumn.push({
name: 'Operation',
key: 'operation',
fieldName: 'operation',
minWidth: 160,
maxWidth: 200,
isResizable: true,
className: 'detail-table',
onRender: (record: any) => {
const trialStatus = record.status;
const flag: boolean = trialStatus === 'RUNNING' || trialStatus === 'UNKNOWN' ? false : true;
return (
<Stack className='detail-button' horizontal>
{/* see intermediate result graph */}
<PrimaryButton
className='detail-button-operation'
title='Intermediate'
onClick={this.showIntermediateModal.bind(this, record)}
>
{LineChart}
</PrimaryButton>
{/* kill job */}
{flag ? (
<PrimaryButton className='detail-button-operation' disabled={true} title='kill'>
{blocked}
</PrimaryButton>
) : (
<KillJob trial={record} />
)}
{/* Add a new trial-customized trial */}
<PrimaryButton
className='detail-button-operation'
title='Customized trial'
onClick={this.setCustomizedTrial.bind(this, record.id)}
disabled={disabledAddCustomizedTrial}
>
{copy}
</PrimaryButton>
</Stack>
);
}
});
break;
case result:
// remove SEARCH_SPACE title
// const realItem = item.replace(' (search space)', '');
showColumn.push({
name: item.replace(' (search space)', ''),
key: item,
fieldName: item,
minWidth: 150,
onRender: (record: TableRecord) => {
const eachTrial = TRIALS.getTrial(record.id);
return <span>{eachTrial.description.parameters[item.replace(' (search space)', '')]}</span>;
}
});
break;
default:
showColumn.push({
name: item,
key: item,
fieldName: item,
minWidth: 100,
onRender: (record: TableRecord) => {
const accDictionary = record.accDictionary;
let other = '';
if (accDictionary !== undefined) {
other = accDictionary[item].toString();
}
return (
<TooltipHost content={other}>
<div className='ellipsis'>{other}</div>
</TooltipHost>
);
}
});
} }
const lengths = tableItems.map(item => `${item[k]}`.length);
const avgLengths = lengths.reduce((a, b) => a + b) / lengths.length;
const columnTitle = _inferColumnTitle(k);
const columnWidth = Math.max(columnTitle.length, avgLengths);
// TODO: add blacklist
columns.push({
name: columnTitle,
key: k,
fieldName: k,
minWidth: columnWidth * 13,
maxWidth: columnWidth * 18,
isResizable: true,
onColumnClick: this._onColumnClick.bind(this),
...(k === 'status' && {
// color status
onRender: (record): React.ReactNode => (
<span className={`${record.status} commonStyle`}>{record.status}</span>
)
}),
...((k.startsWith('metric/') || k.startsWith('space/')) && {
// show tooltip
onRender: (record): React.ReactNode => (
<TooltipHost content={record[k]}>
<div className='ellipsis'>{record[k]}</div>
</TooltipHost>
)
}),
...(k === 'latestAccuracy' && {
// FIXME: this is ad-hoc
onRender: (record): React.ReactNode => (
<TooltipHost content={record._formattedLatestAccuracy}>
<div className='ellipsis'>{record._formattedLatestAccuracy}</div>
</TooltipHost>
)
}),
...(['startTime', 'endTime'].includes(k) && {
onRender: (record): React.ReactNode => <span>{formatTimestamp(record[k], '--')}</span>
}),
...(k === 'duration' && {
onRender: (record): React.ReactNode => (
<span className='durationsty'>{convertDuration(record[k])}</span>
)
})
});
} }
return showColumn; // operations column
}; columns.push({
name: 'Operation',
componentDidMount(): void { key: '_operation',
window.addEventListener('resize', this.onWindowResize); fieldName: 'operation',
this.updateData(); minWidth: 160,
} maxWidth: 200,
isResizable: true,
className: 'detail-table',
onRender: this._renderOperationColumn.bind(this)
});
componentDidUpdate(prevProps: TableListProps): void { const { sortInfo } = this.state;
if ( for (const column of columns) {
this.props.columnList !== prevProps.columnList || if (column.key === sortInfo.field) {
this.props.tableSource !== prevProps.tableSource || column.isSorted = true;
prevProps.trialsUpdateBroadcast !== this.props.trialsUpdateBroadcast column.isSortedDescending = sortInfo.isDescend;
) { } else {
const { columnList } = this.props; column.isSorted = false;
this.setState( column.isSortedDescending = true;
{ }
tableColumns: this.initTableColumnList(columnList),
allColumnList: this.getAllColumnKeys()
},
() => {
this.updateData();
}
);
} }
return columns;
} }
// slice all table data into current page data private _updateTableSource(): void {
updateData(): void { // call this method when trials or the computation of trial filter has changed
const tableSource: Array<TableRecord> = this.props.tableSource; const items = this._trialsToTableItems(this._filterTrials(this.props.tableSource));
const { offset, perPage, sortMessage } = this.state; if (items.length > 0) {
const columns = this._buildColumnsFromTableItems(items);
if (sortMessage.field !== '') { this.setState({
tableSource.sort(function(a, b): any { displayedItems: items,
if ( columns: columns
a[sortMessage.field] === undefined || });
Object.is(a[sortMessage.field], NaN) || } else {
Object.is(a[sortMessage.field], Infinity) || this.setState({
Object.is(a[sortMessage.field], -Infinity) || displayedItems: [],
typeof a[sortMessage.field] === 'object' columns: []
) {
return 1;
}
if (
b[sortMessage.field] === undefined ||
Object.is(b[sortMessage.field], NaN) ||
Object.is(b[sortMessage.field], Infinity) ||
Object.is(b[sortMessage.field], -Infinity) ||
typeof b[sortMessage.field] === 'object'
) {
return -1;
}
return (sortMessage.isDescend
? a[sortMessage.field] < b[sortMessage.field]
: a[sortMessage.field] > b[sortMessage.field])
? 1
: -1;
}); });
} }
}
const tableSlice = tableSource.slice(offset, offset + perPage); private _updateDisplayedColumns(displayedColumns: string[]): void {
const curPageCount = Math.ceil(tableSource.length / perPage);
this.setState({ this.setState({
tablePerPage: tableSlice, displayedColumns: displayedColumns
pageCount: curPageCount
}); });
} }
// update data when click the page index of pagination private _renderOperationColumn(record: any): React.ReactNode {
handlePageClick = (evt: any): void => { const runningTrial: boolean = ['RUNNING', 'UNKNOWN'].includes(record.status) ? false : true;
const selectedPage = evt.selected; const disabledAddCustomizedTrial = ['DONE', 'ERROR', 'STOPPED'].includes(EXPERIMENT.status);
const offset = selectedPage * this.state.perPage; return (
<Stack className='detail-button' horizontal>
this.setState( <PrimaryButton
{ className='detail-button-operation'
currentPage: selectedPage, title='Intermediate'
offset: offset onClick={(): void => {
}, const { tableSource } = this.props;
() => { const trial = tableSource.find(trial => trial.id === record.id) as TableObj;
this.updateData(); this.setState({ intermediateDialogTrial: trial });
} }}
>
{LineChart}
</PrimaryButton>
{runningTrial ? (
<PrimaryButton className='detail-button-operation' disabled={true} title='kill'>
{blocked}
</PrimaryButton>
) : (
<KillJob trial={record} />
)}
<PrimaryButton
className='detail-button-operation'
title='Customized trial'
onClick={(): void => {
this.setState({ copiedTrialId: record.id });
}}
disabled={disabledAddCustomizedTrial}
>
{copy}
</PrimaryButton>
</Stack>
); );
}; }
// update per page items when click the dropdown of pagination
updatePerPage = (event: React.FormEvent<HTMLDivElement>, item: IDropdownOption | undefined): void => {
const { pageCount } = this.state;
if (item !== undefined) {
const currentPerPage = item.key === 'all' ? this.props.tableSource.length : Number(item.key);
const currentPageCount = this.props.tableSource.length <= currentPerPage ? 1 : pageCount;
this.setState( componentDidUpdate(prevProps: TableListProps): void {
{ if (this.props.tableSource !== prevProps.tableSource) {
perPage: currentPerPage, this._updateTableSource();
offset: 0,
currentPage: 0,
pageCount: currentPageCount
},
() => {
this.updateData();
}
);
} }
}; }
componentDidMount(): void {
this._updateTableSource();
}
render(): React.ReactNode { render(): React.ReactNode {
const { const {
intermediateKey, displayedItems,
modalIntermediateWidth, columns,
modalIntermediateHeight, searchType,
tableColumns, customizeColumnsDialogVisible,
allColumnList, compareDialogVisible,
isShowColumn, displayedColumns,
modalVisible, selectedRowIds,
selectRows, intermediateDialogTrial,
isShowCompareModal, copiedTrialId
intermediateOtherKeys,
isShowCustomizedModal,
copyTrialId,
intermediateOption,
tablePerPage
} = this.state; } = this.state;
const { columnList } = this.props;
const perPageOptions = [
{ key: '10', text: '10 items per page' },
{ key: '20', text: '20 items per page' },
{ key: '50', text: '50 items per page' },
{ key: 'all', text: 'All items' }
];
return ( return (
<Stack> <div id='tableList'>
<div id='tableList'> <Stack horizontal className='panelTitle' style={{ marginTop: 10 }}>
<DetailsList <span style={{ marginRight: 12 }}>{tableListIcon}</span>
columns={tableColumns} <span>Trial jobs</span>
items={tablePerPage} </Stack>
setKey='set' <Stack horizontal className='allList'>
compact={true} <StackItem grow={50}>
onRenderRow={this.onRenderRow} <DefaultButton
layoutMode={DetailsListLayoutMode.justified} text='Compare'
selectionMode={SelectionMode.multiple} className='allList-compare'
selection={this.getSelectedRows} onClick={(): void => {
/> this.setState({ compareDialogVisible: true });
}}
<Stack disabled={selectedRowIds.length === 0}
horizontal
horizontalAlign='end'
verticalAlign='baseline'
styles={{ root: { padding: 10 } }}
tokens={horizontalGapStackTokens}
>
<Dropdown
selectedKey={
this.state.perPage === this.props.tableSource.length
? 'all'
: String(this.state.perPage)
}
options={perPageOptions}
onChange={this.updatePerPage}
styles={{ dropdown: { width: 150 } }}
/>
<ReactPaginate
previousLabel={'<'}
nextLabel={'>'}
breakLabel={'...'}
breakClassName={'break'}
pageCount={this.state.pageCount}
marginPagesDisplayed={2}
pageRangeDisplayed={2}
onPageChange={this.handlePageClick}
containerClassName={this.props.tableSource.length == 0 ? 'pagination hidden' : 'pagination'}
subContainerClassName={'pages pagination'}
disableInitialCallback={false}
activeClassName={'active'}
forcePage={this.state.currentPage}
/>
</Stack>
</div>
{/* Intermediate Result Modal */}
<Modal
isOpen={modalVisible}
onDismiss={this.hideIntermediateModal}
containerClassName={contentStyles.container}
>
<div className={contentStyles.header}>
<span>Intermediate result</span>
<IconButton
styles={iconButtonStyles}
iconProps={{ iconName: 'Cancel' }}
ariaLabel='Close popup modal'
onClick={this.hideIntermediateModal as any}
/> />
</div> </StackItem>
{intermediateOtherKeys.length > 1 ? ( <StackItem grow={50}>
<Stack horizontalAlign='end' className='selectKeys'> <Stack horizontal horizontalAlign='end' className='allList'>
<DefaultButton
className='allList-button-gap'
text='Add/Remove columns'
onClick={(): void => {
this.setState({ customizeColumnsDialogVisible: true });
}}
/>
<Dropdown <Dropdown
className='select' selectedKey={searchType}
selectedKey={intermediateKey} options={Object.entries(searchOptionLiterals).map(([k, v]) => ({
options={intermediateOtherKeys.map((key, item) => { key: k,
return { text: v
key: key, }))}
text: intermediateOtherKeys[item] onChange={this._updateSearchFilterType.bind(this)}
}; styles={{ root: { width: 150 } }}
})} />
onChange={this.selectOtherKeys} <input
type='text'
className='allList-search-input'
placeholder={`Search by ${
['id', 'trialnum'].includes(searchType)
? searchOptionLiterals[searchType]
: searchType
}`}
onChange={this._updateSearchText.bind(this)}
style={{ width: 230 }}
/> />
</Stack> </Stack>
) : null} </StackItem>
<div className='intermediate-graph'> </Stack>
<ReactEcharts {columns && displayedItems && (
option={intermediateOption} <PaginationTable
style={{ columns={columns.filter(
width: 0.5 * modalIntermediateWidth, column =>
height: 0.7 * modalIntermediateHeight, displayedColumns.includes(column.key) || ['_expand', '_operation'].includes(column.key)
maxHeight: 534, )}
padding: 20 items={displayedItems}
}} compact={true}
theme='my_theme' selection={this._selection}
/> selectionMode={SelectionMode.multiple}
<div className='xAxis'>#Intermediate result</div> selectionPreservedOnEmptyClick={true}
</div> onRenderRow={(props): any => {
</Modal> // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
{/* Add Column Modal */} return <ExpandableDetails detailsProps={props!} isExpand={props!.item._expandDetails} />;
{isShowColumn && ( }}
/>
)}
{compareDialogVisible && (
<Compare
title='Compare trials'
showDetails={true}
trials={this.props.tableSource.filter(trial => selectedRowIds.includes(trial.id))}
onHideDialog={(): void => {
this.setState({ compareDialogVisible: false });
}}
/>
)}
{intermediateDialogTrial !== undefined && (
<Compare
title='Intermediate results'
showDetails={false}
trials={[intermediateDialogTrial]}
onHideDialog={(): void => {
this.setState({ intermediateDialogTrial: undefined });
}}
/>
)}
{customizeColumnsDialogVisible && (
<ChangeColumnComponent <ChangeColumnComponent
hideShowColumnDialog={this.hideShowColumnModal} selectedColumns={displayedColumns}
isHideDialog={!isShowColumn} allColumns={columns
showColumn={allColumnList} .filter(column => !column.key.startsWith('_'))
selectedColumn={columnList} .map(column => ({ key: column.key, name: column.name }))}
changeColumn={this.props.changeColumn} onSelectedChange={this._updateDisplayedColumns.bind(this)}
onHideDialog={(): void => {
this.setState({ customizeColumnsDialogVisible: false });
}}
/> />
)} )}
{/* compare trials based message */} {/* Clone a trial and customize a set of new parameters */}
{isShowCompareModal && <Compare compareStacks={selectRows} cancelFunc={this.hideCompareModal} />} {/* visible is done inside because prompt is needed even when the dialog is closed */}
{/* clone trial parameters and could submit a customized trial */}
<Customize <Customize
visible={isShowCustomizedModal} visible={copiedTrialId !== undefined}
copyTrialId={copyTrialId} copyTrialId={copiedTrialId || ''}
closeCustomizeModal={this.closeCustomizedTrial} closeCustomizeModal={(): void => {
this.setState({ copiedTrialId: undefined });
}}
/> />
</Stack> </div>
); );
} }
} }
......
...@@ -5,13 +5,20 @@ import { BrowserRouter as Router, Route, Switch, Redirect } from 'react-router-d ...@@ -5,13 +5,20 @@ import { BrowserRouter as Router, Route, Switch, Redirect } from 'react-router-d
const Overview = lazy(() => import('./components/Overview')); const Overview = lazy(() => import('./components/Overview'));
const TrialsDetail = lazy(() => import('./components/TrialsDetail')); const TrialsDetail = lazy(() => import('./components/TrialsDetail'));
import './index.css'; import './index.css';
import './static/style/loading.scss';
import * as serviceWorker from './serviceWorker'; import * as serviceWorker from './serviceWorker';
ReactDOM.render( ReactDOM.render(
<Router> <Router>
<App> <App>
<Switch> <Switch>
<Suspense fallback={null}> <Suspense
fallback={
<div className='loading'>
<img src={require('./static/img/loading.gif')} />
</div>
}
>
<Route path='/oview' component={Overview} /> <Route path='/oview' component={Overview} />
<Route path='/detail' component={TrialsDetail} /> <Route path='/detail' component={TrialsDetail} />
<Route path='/' render={(): React.ReactNode => <Redirect to={{ pathname: '/oview' }} />} /> <Route path='/' render={(): React.ReactNode => <Redirect to={{ pathname: '/oview' }} />} />
......
...@@ -15,7 +15,7 @@ const trialJobStatus = [ ...@@ -15,7 +15,7 @@ const trialJobStatus = [
'SYS_CANCELED', 'SYS_CANCELED',
'EARLY_STOPPED' 'EARLY_STOPPED'
]; ];
const CONTROLTYPE = ['SEARCH_SPACE', 'TRIAL_CONCURRENCY', 'MAX_EXEC_DURATION']; const CONTROLTYPE = ['MAX_EXEC_DURATION', 'MAX_TRIAL_NUM', 'TRIAL_CONCURRENCY', 'SEARCH_SPACE'];
const MONACO = { const MONACO = {
readOnly: true, readOnly: true,
automaticLayout: true, automaticLayout: true,
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment