Commit b40e3db7 authored by quzha's avatar quzha
Browse files

Merge branch 'master' of github.com:Microsoft/nni into dev-retiarii

parents efa4e31c 95f731e4
...@@ -6,24 +6,29 @@ export const Command1 = (): any => { ...@@ -6,24 +6,29 @@ export const Command1 = (): any => {
const tuner = EXPERIMENT.profile.params.tuner; const tuner = EXPERIMENT.profile.params.tuner;
const advisor = EXPERIMENT.profile.params.advisor; const advisor = EXPERIMENT.profile.params.advisor;
const assessor = EXPERIMENT.profile.params.assessor; const assessor = EXPERIMENT.profile.params.assessor;
let title = ''; const title: string[] = [];
let builtinName = ''; const builtinName: string[] = [];
if (tuner !== undefined) { if (tuner !== undefined) {
title = title.concat('Tuner'); title.push('Tuner');
if (tuner.builtinTunerName !== undefined) { if (tuner.builtinTunerName !== undefined) {
builtinName = builtinName.concat(tuner.builtinTunerName); builtinName.push(tuner.builtinTunerName);
} }
} }
if (advisor !== undefined) { if (advisor !== undefined) {
title = title.concat('/ Assessor'); title.push('Advisor');
if (advisor.builtinAdvisorName !== undefined) { if (advisor.builtinAdvisorName !== undefined) {
builtinName = builtinName.concat(advisor.builtinAdvisorName); builtinName.push(advisor.builtinAdvisorName);
}
if (advisor.className !== undefined) {
builtinName.push(advisor.className);
} }
} }
if (assessor !== undefined) { if (assessor !== undefined) {
title = title.concat('/ Addvisor'); title.push('Assessor');
if (assessor.builtinAssessorName !== undefined) { if (assessor.builtinAssessorName !== undefined) {
builtinName = builtinName.concat(assessor.builtinAssessorName); builtinName.push(assessor.builtinAssessorName);
} }
} }
...@@ -32,8 +37,8 @@ export const Command1 = (): any => { ...@@ -32,8 +37,8 @@ export const Command1 = (): any => {
<div> <div>
<p className='command'>Training platform</p> <p className='command'>Training platform</p>
<div className='nowrap'>{EXPERIMENT.profile.params.trainingServicePlatform}</div> <div className='nowrap'>{EXPERIMENT.profile.params.trainingServicePlatform}</div>
<p className='lineMargin'>{title}</p> <p className='lineMargin'>{title.join('/')}</p>
<div className='nowrap'>{builtinName}</div> <div className='nowrap'>{builtinName.join('/')}</div>
</div> </div>
</div> </div>
); );
......
...@@ -188,16 +188,16 @@ export const EditExperimentParam = (): any => { ...@@ -188,16 +188,16 @@ export const EditExperimentParam = (): any => {
/> />
)} )}
{isShowPencil && ( {isShowPencil && (
<span className='edit' onClick={hidePencil}> <span className='edit cursor' onClick={hidePencil}>
{Edit} {Edit}
</span> </span>
)} )}
{!isShowPencil && ( {!isShowPencil && (
<span className='series'> <span className='series'>
<span className='confirm' onClick={confirmEdit}> <span className='confirm cursor' onClick={confirmEdit}>
{CheckMark} {CheckMark}
</span> </span>
<span className='cancel' onClick={cancelEdit}> <span className='cancel cursor' onClick={cancelEdit}>
{Cancel} {Cancel}
</span> </span>
</span> </span>
......
...@@ -6,7 +6,7 @@ import { convertDuration, convertTimeAsUnit } from '../../../static/function'; ...@@ -6,7 +6,7 @@ import { convertDuration, convertTimeAsUnit } from '../../../static/function';
import { EditExperimentParam } from './EditExperimentParam'; import { EditExperimentParam } from './EditExperimentParam';
import { ExpDurationContext } from './ExpDurationContext'; import { ExpDurationContext } from './ExpDurationContext';
import { EditExpeParamContext } from './context'; import { EditExpeParamContext } from './context';
import { durationItem1, durationItem2 } from './commonStyle'; import { leftProgress, durationItem2, progressHeight } from './commonStyle';
import '../../../static/style/overview/count.scss'; import '../../../static/style/overview/count.scss';
export const ExpDuration = (): any => ( export const ExpDuration = (): any => (
...@@ -19,7 +19,7 @@ export const ExpDuration = (): any => ( ...@@ -19,7 +19,7 @@ export const ExpDuration = (): any => (
const maxExecDurationStr = convertTimeAsUnit(maxDurationUnit, maxExecDuration).toString(); const maxExecDurationStr = convertTimeAsUnit(maxDurationUnit, maxExecDuration).toString();
return ( return (
<Stack horizontal className='ExpDuration'> <Stack horizontal className='ExpDuration'>
<div style={durationItem1}> <div style={leftProgress}>
<TooltipHost <TooltipHost
content={`${convertDuration(tooltip)} remaining`} content={`${convertDuration(tooltip)} remaining`}
directionalHint={DirectionalHint.bottomCenter} directionalHint={DirectionalHint.bottomCenter}
...@@ -33,7 +33,11 @@ export const ExpDuration = (): any => ( ...@@ -33,7 +33,11 @@ export const ExpDuration = (): any => (
} }
}} }}
> >
<ProgressIndicator className={EXPERIMENT.status} percentComplete={percent} barHeight={15} /> <ProgressIndicator
className={EXPERIMENT.status}
percentComplete={percent}
barHeight={progressHeight}
/>
</TooltipHost> </TooltipHost>
{/* execDuration / maxDuration: 20min / 1h */} {/* execDuration / maxDuration: 20min / 1h */}
<div className='exp-progress'> <div className='exp-progress'>
......
...@@ -5,7 +5,7 @@ import { CONTROLTYPE, TOOLTIP_BACKGROUND_COLOR, MAX_TRIAL_NUMBERS } from '../../ ...@@ -5,7 +5,7 @@ import { CONTROLTYPE, TOOLTIP_BACKGROUND_COLOR, MAX_TRIAL_NUMBERS } from '../../
import { EditExperimentParam } from './EditExperimentParam'; import { EditExperimentParam } from './EditExperimentParam';
import { EditExpeParamContext } from './context'; import { EditExpeParamContext } from './context';
import { ExpDurationContext } from './ExpDurationContext'; import { ExpDurationContext } from './ExpDurationContext';
import { trialCountItem1, trialCountItem2 } from './commonStyle'; import { leftProgress, trialCountItem2, progressHeight } from './commonStyle';
export const TrialCount = (): any => { export const TrialCount = (): any => {
const count = TRIALS.countStatus(); const count = TRIALS.countStatus();
...@@ -23,9 +23,9 @@ export const TrialCount = (): any => { ...@@ -23,9 +23,9 @@ export const TrialCount = (): any => {
return ( return (
<React.Fragment> <React.Fragment>
<Stack horizontal horizontalAlign='space-between' className='ExpDuration'> <Stack horizontal horizontalAlign='space-between' className='ExpDuration'>
<div style={trialCountItem1}> <div style={leftProgress}>
<TooltipHost <TooltipHost
content={bar2.toString()} content={`${bar2.toString()} trials`}
directionalHint={DirectionalHint.bottomCenter} directionalHint={DirectionalHint.bottomCenter}
tooltipProps={{ tooltipProps={{
calloutProps: { calloutProps: {
...@@ -40,7 +40,7 @@ export const TrialCount = (): any => { ...@@ -40,7 +40,7 @@ export const TrialCount = (): any => {
<ProgressIndicator <ProgressIndicator
className={EXPERIMENT.status} className={EXPERIMENT.status}
percentComplete={bar2Percent} percentComplete={bar2Percent}
barHeight={15} barHeight={progressHeight}
/> />
</TooltipHost> </TooltipHost>
<div className='exp-progress'> <div className='exp-progress'>
...@@ -81,7 +81,7 @@ export const TrialCount = (): any => { ...@@ -81,7 +81,7 @@ export const TrialCount = (): any => {
</EditExpeParamContext.Provider> </EditExpeParamContext.Provider>
</div> </div>
</Stack> </Stack>
<Stack horizontal horizontalAlign='space-between' className='mess'> <Stack horizontal horizontalAlign='space-between' className='trialStatus'>
<div className='basic'> <div className='basic'>
<p>Running</p> <p>Running</p>
<div>{count.get('RUNNING')}</div> <div>{count.get('RUNNING')}</div>
......
const durationItem1: React.CSSProperties = { const leftProgress: React.CSSProperties = {
width: '33%' width: '33%',
position: 'relative',
top: 6
}; };
const durationItem2: React.CSSProperties = { const durationItem2: React.CSSProperties = {
width: '52%', width: '51.5%',
paddingLeft: '15%' paddingLeft: '15%'
}; };
const trialCountItem1: React.CSSProperties = {
width: '33%'
};
const trialCountItem2: React.CSSProperties = { const trialCountItem2: React.CSSProperties = {
width: '52%' width: '51.5%'
}; };
const progressHeight = 8;
export { durationItem1, durationItem2, trialCountItem1, trialCountItem2 }; export { leftProgress, durationItem2, trialCountItem2, progressHeight };
...@@ -23,76 +23,74 @@ export const ReBasicInfo = (): any => { ...@@ -23,76 +23,74 @@ export const ReBasicInfo = (): any => {
return ( return (
<div> <div>
<div className='basic'> <Stack horizontal horizontalAlign='space-between' className='mess'>
<p> <div className='basic'>
ID: <span>{EXPERIMENT.profile.id}</span> <p>Name</p>
</p> <div className='nowrap'>{EXPERIMENT.profile.params.experimentName}</div>
<div>{EXPERIMENT.profile.params.experimentName}</div> <p className='margin'>ID</p>
</div> <div className='nowrap'>{EXPERIMENT.profile.id}</div>
<div className='basic'> </div>
<p>Status</p> <div className='basic'>
<Stack horizontal className='status'> <p>Status</p>
<span className={`${EXPERIMENT.status} status-text`}>{EXPERIMENT.status}</span> <Stack horizontal className='status'>
{EXPERIMENT.status === 'ERROR' ? ( <span className={`${EXPERIMENT.status} status-text`}>{EXPERIMENT.status}</span>
<div> {EXPERIMENT.status === 'ERROR' ? (
<div className={styles.buttonArea} ref={ref}> <div>
<IconButton <div className={styles.buttonArea} ref={ref}>
iconProps={{ iconName: 'info' }} <IconButton
onClick={isCalloutVisible ? onDismiss : showCallout} iconProps={{ iconName: 'info' }}
/> onClick={isCalloutVisible ? onDismiss : showCallout}
</div> />
{isCalloutVisible && ( </div>
<Callout {isCalloutVisible && (
className={styles.callout} <Callout
ariaLabelledBy={labelId} className={styles.callout}
ariaDescribedBy={descriptionId} ariaLabelledBy={labelId}
role='alertdialog' ariaDescribedBy={descriptionId}
gapSpace={0} role='alertdialog'
target={ref} gapSpace={0}
onDismiss={onDismiss} target={ref}
setInitialFocus={true} onDismiss={onDismiss}
> setInitialFocus={true}
<div className={styles.header}> >
<p className={styles.title} id={labelId} style={{ color: '#333' }}> <div className={styles.header}>
Error <p className={`${styles.title} color`} id={labelId}>
</p> Error
</div> </p>
<div className={styles.inner}> </div>
<p className={styles.subtext} id={descriptionId} style={{ color: '#333' }}> <div className={styles.inner}>
{EXPERIMENT.error} <p className={`${styles.subtext} color`} id={descriptionId}>
</p> {EXPERIMENT.error}
<div className={styles.actions}> </p>
<Link className={styles.link} onClick={ShowLogDrawer}> <div className={styles.actions}>
Learn about <Link className={styles.link} onClick={ShowLogDrawer}>
</Link> Learn about
</Link>
</div>
</div> </div>
</div> </Callout>
</Callout> )}
)}
</div>
) : null}
</Stack>
</div>
<div className='basic'>
<BestMetricContext.Consumer>
{(value): React.ReactNode => (
<Stack className='bestMetric'>
<p>Best metric</p>
<div className={EXPERIMENT.status}>
{isNaN(value.bestAccuracy) ? 'N/A' : value.bestAccuracy.toFixed(6)}
</div> </div>
</Stack> ) : null}
)} </Stack>
</BestMetricContext.Consumer> <BestMetricContext.Consumer>
</div> {(value): React.ReactNode => (
<div className='basic'> <Stack className='bestMetric'>
<p>Start time</p> <p className='margin'>Best metric</p>
<div className='nowrap'>{formatTimestamp(EXPERIMENT.profile.startTime)}</div> <div className={EXPERIMENT.status}>
</div> {isNaN(value.bestAccuracy) ? 'N/A' : value.bestAccuracy.toFixed(6)}
<div className='basic'> </div>
<p>End time</p> </Stack>
<div className='nowrap'>{formatTimestamp(EXPERIMENT.profile.endTime)}</div> )}
</div> </BestMetricContext.Consumer>
</div>
<div className='basic'>
<p>Start time</p>
<div className='nowrap'>{formatTimestamp(EXPERIMENT.profile.startTime)}</div>
<p className='margin'>End time</p>
<div className='nowrap'>{formatTimestamp(EXPERIMENT.profile.endTime)}</div>
</div>
</Stack>
{/* learn about click -> default active key is dispatcher. */} {/* learn about click -> default active key is dispatcher. */}
{isShowLogDrawer ? <LogDrawer closeDrawer={closeLogDrawer} activeTab='dispatcher' /> : null} {isShowLogDrawer ? <LogDrawer closeDrawer={closeLogDrawer} activeTab='dispatcher' /> : null}
</div> </div>
......
import * as React from 'react';
import { DetailsRow, IDetailsRowBaseProps } from '@fluentui/react';
import OpenRow from '../../public-child/OpenRow';
interface DetailsProps {
detailsProps: IDetailsRowBaseProps;
}
interface DetailsState {
isExpand: boolean;
}
class Details extends React.Component<DetailsProps, DetailsState> {
constructor(props: DetailsProps) {
super(props);
this.state = { isExpand: false };
}
render(): React.ReactNode {
const { detailsProps } = this.props;
const { isExpand } = this.state;
return (
<div>
<div
onClick={(): void => {
this.setState(() => ({ isExpand: !isExpand }));
}}
>
<DetailsRow {...detailsProps} />
</div>
{isExpand && <OpenRow trialId={detailsProps.item.id} />}
</div>
);
}
}
export default Details;
import * as React from 'react'; import * as React from 'react';
import { DetailsList, IDetailsListProps, IColumn } from '@fluentui/react'; import {
DetailsList,
IDetailsListProps,
IColumn,
Icon,
DetailsRow,
IRenderFunction,
IDetailsHeaderProps,
Sticky,
StickyPositionType,
ScrollablePane,
ScrollbarVisibility
} from '@fluentui/react';
import DefaultMetric from '../../public-child/DefaultMetric'; import DefaultMetric from '../../public-child/DefaultMetric';
import Details from './Details'; import OpenRow from '../../public-child/OpenRow';
import { convertDuration } from '../../../static/function'; import { convertDuration, copyAndSort } from '../../../static/function';
import { TRIALS } from '../../../static/datamodel'; import { TRIALS } from '../../../static/datamodel';
import { SortInfo } from '../../../static/interface';
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/tableStatus.css'; import '../../../static/style/tableStatus.css';
...@@ -11,12 +24,15 @@ import '../../../static/style/openRow.scss'; ...@@ -11,12 +24,15 @@ import '../../../static/style/openRow.scss';
interface SuccessTableProps { interface SuccessTableProps {
trialIds: string[]; trialIds: string[];
// eslint-disable-next-line @typescript-eslint/no-unused-vars
updateOverviewPage: () => void;
} }
interface SuccessTableState { interface SuccessTableState {
columns: IColumn[]; columns: IColumn[];
source: Array<any>; source: Array<any>;
innerWidth: number; expandRowIdList: Set<string>;
sortInfo: SortInfo;
} }
class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState> { class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState> {
...@@ -25,18 +41,42 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState> ...@@ -25,18 +41,42 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState>
this.state = { this.state = {
columns: this.columns, columns: this.columns,
source: TRIALS.table(this.props.trialIds), source: TRIALS.table(this.props.trialIds),
innerWidth: window.innerWidth sortInfo: { field: '', isDescend: false },
expandRowIdList: new Set() // store expanded row's trial id
}; };
} }
private onRenderRow: IDetailsListProps['onRenderRow'] = props => { componentDidUpdate(prevProps: SuccessTableProps): void {
if (props) { if (this.props.trialIds !== prevProps.trialIds) {
return <Details detailsProps={props} />; const { trialIds } = this.props;
this.setState(() => ({ source: TRIALS.table(trialIds) }));
} }
return null; }
};
onColumnClick = (ev: React.MouseEvent<HTMLElement>, getColumn: IColumn): void => { render(): React.ReactNode {
const { columns, source, sortInfo } = this.state;
const keepSortedSource = copyAndSort(source, sortInfo.field, sortInfo.isDescend);
const isNoneData = source.length === 0 ? true : false;
return (
<div id='succTable'>
<ScrollablePane className='scrollPanel' scrollbarVisibility={ScrollbarVisibility.auto}>
<DetailsList
columns={columns}
items={keepSortedSource}
setKey='set'
compact={true}
onRenderRow={this.onRenderRow}
onRenderDetailsHeader={this.onRenderDetailsHeader}
selectionMode={0} // close selector function
className='succTable'
/>
</ScrollablePane>
{isNoneData && <div className='succTable-tooltip'>{this.tooltipStr}</div>}
</div>
);
}
private onColumnClick = (_ev: React.MouseEvent<HTMLElement>, getColumn: IColumn): void => {
const { columns, source } = this.state; const { columns, source } = this.state;
const newColumns: IColumn[] = columns.slice(); const newColumns: IColumn[] = columns.slice();
const currColumn: IColumn = newColumns.filter(item => getColumn.key === item.key)[0]; const currColumn: IColumn = newColumns.filter(item => getColumn.key === item.key)[0];
...@@ -50,32 +90,51 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState> ...@@ -50,32 +90,51 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState>
} }
}); });
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const newItems = this.copyAndSort(source, currColumn.fieldName!, currColumn.isSortedDescending); const newItems = copyAndSort(source, currColumn.fieldName!, currColumn.isSortedDescending);
this.setState({ this.setState({
columns: newColumns, columns: newColumns,
source: newItems source: newItems,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
sortInfo: { field: currColumn.fieldName!, isDescend: currColumn.isSortedDescending }
}); });
}; };
private copyAndSort<T>(items: T[], columnKey: string, isSortedDescending?: boolean): T[] { private tooltipStr = (
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));
}
tooltipStr = (
<React.Fragment> <React.Fragment>
The experiment is running, please wait for the final metric patiently. You could also find status of trial The experiment is running, please wait for the final metric patiently. You could also find status of trial
job with <span>{DETAILTABS}</span> button. job with <span>{DETAILTABS}</span> button.
</React.Fragment> </React.Fragment>
); );
columns = [ private columns = [
{
key: '_expand',
name: '',
onRender: (item: any): any => (
<Icon
aria-hidden={true}
iconName='ChevronRight'
styles={{
root: {
transition: 'all 0.2s',
transform: `rotate(${this.state.expandRowIdList.has(item.id) ? 90 : 0}deg)`
}
}}
className='cursor'
onClick={this.expandTrialId.bind(this, Event, item.id)}
/>
),
fieldName: 'expand',
isResizable: false,
minWidth: 20,
maxWidth: 20
},
{ {
name: 'Trial No.', name: 'Trial No.',
key: 'sequenceId', key: 'sequenceId',
fieldName: 'sequenceId', // required! fieldName: 'sequenceId', // required!
minWidth: 50, minWidth: 60,
maxWidth: 87, maxWidth: 100,
isResizable: true, isResizable: true,
data: 'number', data: 'number',
onColumnClick: this.onColumnClick, onColumnClick: this.onColumnClick,
...@@ -85,8 +144,8 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState> ...@@ -85,8 +144,8 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState>
name: 'ID', name: 'ID',
key: 'id', key: 'id',
fieldName: 'id', fieldName: 'id',
minWidth: 50, minWidth: 60,
maxWidth: 87, maxWidth: 118,
isResizable: true, isResizable: true,
className: 'tableHead leftTitle', className: 'tableHead leftTitle',
data: 'string', data: 'string',
...@@ -96,8 +155,8 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState> ...@@ -96,8 +155,8 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState>
{ {
name: 'Duration', name: 'Duration',
key: 'duration', key: 'duration',
minWidth: 65, minWidth: 85,
maxWidth: 150, maxWidth: 166,
isResizable: true, isResizable: true,
fieldName: 'duration', fieldName: 'duration',
data: 'number', data: 'number',
...@@ -111,8 +170,8 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState> ...@@ -111,8 +170,8 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState>
{ {
name: 'Status', name: 'Status',
key: 'status', key: 'status',
minWidth: 80, minWidth: 98,
maxWidth: 150, maxWidth: 160,
isResizable: true, isResizable: true,
fieldName: 'status', fieldName: 'status',
onRender: (item: any): React.ReactNode => ( onRender: (item: any): React.ReactNode => (
...@@ -124,7 +183,7 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState> ...@@ -124,7 +183,7 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState>
key: 'accuracy', key: 'accuracy',
fieldName: 'accuracy', fieldName: 'accuracy',
minWidth: 100, minWidth: 100,
maxWidth: 160, maxWidth: 166,
isResizable: true, isResizable: true,
data: 'number', data: 'number',
onColumnClick: this.onColumnClick, onColumnClick: this.onColumnClick,
...@@ -132,43 +191,49 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState> ...@@ -132,43 +191,49 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState>
} }
]; ];
setInnerWidth = (): void => { private onRenderDetailsHeader: IRenderFunction<IDetailsHeaderProps> = (props, defaultRender) => {
this.setState(() => ({ innerWidth: window.innerWidth })); if (!props) {
return null;
}
return (
<Sticky stickyPosition={StickyPositionType.Header} isScrollSynced>
{// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
defaultRender!({
...props
})}
</Sticky>
);
}; };
componentDidMount(): void { private onRenderRow: IDetailsListProps['onRenderRow'] = props => {
window.addEventListener('resize', this.setInnerWidth); const { expandRowIdList } = this.state;
} if (props) {
componentWillUnmount(): void { return (
window.removeEventListener('resize', this.setInnerWidth); <div>
} <div>
<DetailsRow {...props} />
componentDidUpdate(prevProps: SuccessTableProps): void { </div>
if (this.props.trialIds !== prevProps.trialIds) { {Array.from(expandRowIdList).map(
const { trialIds } = this.props; item => item === props.item.id && <OpenRow key={item} trialId={item} />
this.setState(() => ({ source: TRIALS.table(trialIds) })); )}
</div>
);
} }
} return null;
};
render(): React.ReactNode {
const { columns, source } = this.state;
const isNoneData = source.length === 0 ? true : false;
return ( private expandTrialId = (_event: any, id: string): void => {
<div id='succTable'> const { expandRowIdList } = this.state;
<DetailsList const { updateOverviewPage } = this.props;
columns={columns} const copyExpandList = expandRowIdList;
items={source} if (copyExpandList.has(id)) {
setKey='set' copyExpandList.delete(id);
compact={true} } else {
onRenderRow={this.onRenderRow} copyExpandList.add(id);
selectionMode={0} // close selector function }
className='succTable' this.setState(() => ({ expandRowIdList: copyExpandList }));
/> updateOverviewPage();
{isNoneData && <div className='succTable-tooltip'>{this.tooltipStr}</div>} };
</div>
);
}
} }
export default SuccessTable; export default SuccessTable;
...@@ -27,7 +27,7 @@ class MonacoHTML extends React.Component<MonacoEditorProps, {}> { ...@@ -27,7 +27,7 @@ class MonacoHTML extends React.Component<MonacoEditorProps, {}> {
render(): React.ReactNode { render(): React.ReactNode {
const { content, loading, height } = this.props; const { content, loading, height } = this.props;
return ( return (
<div className='just-for-log'> <React.Fragment>
{loading ? ( {loading ? (
<Spinner <Spinner
label='Wait, wait...' label='Wait, wait...'
...@@ -46,7 +46,7 @@ class MonacoHTML extends React.Component<MonacoEditorProps, {}> { ...@@ -46,7 +46,7 @@ class MonacoHTML extends React.Component<MonacoEditorProps, {}> {
) : ( ) : (
<MonacoEditor width='100%' height={height} language='json' value={content} options={DRAWEROPTION} /> <MonacoEditor width='100%' height={height} language='json' value={content} options={DRAWEROPTION} />
)} )}
</div> </React.Fragment>
); );
} }
} }
......
import React from 'react';
import { DefaultButton, Icon } from '@fluentui/react';
interface ButtonProps {
icon: string;
btuName: string;
event: any;
}
class IconButtonTemplet extends React.Component<ButtonProps, {}> {
constructor(props: ButtonProps) {
super(props);
}
render(): React.ReactNode {
const { icon, btuName, event } = this.props;
return (
<div className='container'>
<DefaultButton className='icon'>
<Icon iconName={icon} />
</DefaultButton>
<DefaultButton className='integralBtn' onClick={event}>
<Icon iconName={icon} />
<span className='margin'>{btuName}</span>
</DefaultButton>
</div>
);
}
}
export default IconButtonTemplet;
import React, { useState, useCallback } from 'react'; import React, { useState, useCallback } from 'react';
import { DefaultButton, Stack } from '@fluentui/react'; import { Stack } from '@fluentui/react';
import TrialConfigPanel from './TrialConfigPanel'; import TrialConfigPanel from './TrialConfigPanel';
import '../../../static/style/overview/config.scss'; import LogPanel from '../modals/LogPanel';
import IconButtonTemplate from './IconButtonTemplet';
import '../../static/style/overview/panel.scss';
export const TrialConfigButton = (): any => { export const SlideNavBtns = (): any => {
const [isShowConfigPanel, setShowConfigPanle] = useState(false); const [isShowConfigPanel, setShowConfigPanle] = useState(false);
const [activeTab, setActiveTab] = useState('1'); const [isShowLogPanel, setShowLogPanel] = useState(false);
const [panelName, setPanelName] = useState('');
const hideConfigPanel = useCallback(() => setShowConfigPanle(false), []); const hideConfigPanel = useCallback(() => setShowConfigPanle(false), []);
const showTrialConfigpPanel = useCallback(() => { const showTrialConfigpPanel = useCallback(() => {
setShowConfigPanle(true); setShowConfigPanle(true);
setActiveTab('config'); setPanelName('config');
}, []); }, []);
const showSearchSpacePanel = useCallback(() => { const showSearchSpacePanel = useCallback(() => {
setShowConfigPanle(true); setShowConfigPanle(true);
setActiveTab('search space'); setPanelName('search space');
}, []);
const showLogPanel = useCallback(() => {
setShowLogPanel(true);
}, []);
const hideLogPanel = useCallback(() => {
setShowLogPanel(false);
}, []); }, []);
return ( return (
// right side nav buttons
<React.Fragment> <React.Fragment>
<Stack className='config'> <Stack className='config'>
<DefaultButton text='Search space' onClick={showSearchSpacePanel} /> <IconButtonTemplate icon='DocumentSearch' btuName='Search space' event={showSearchSpacePanel} />
<DefaultButton text='Config' onClick={showTrialConfigpPanel} /> <IconButtonTemplate icon='Archive' btuName='Config' event={showTrialConfigpPanel} />
<IconButtonTemplate icon='FilePDB' btuName='Log files' event={showLogPanel} />
</Stack> </Stack>
{isShowConfigPanel && <TrialConfigPanel hideConfigPanel={hideConfigPanel} activeTab={activeTab} />} {isShowConfigPanel && <TrialConfigPanel panelName={panelName} hideConfigPanel={hideConfigPanel} />}
{/* the panel for dispatcher & nnimanager log message */}
{isShowLogPanel && <LogPanel closeDrawer={hideLogPanel} />}
</React.Fragment> </React.Fragment>
); );
}; };
import * as React from 'react'; import * as React from 'react';
import { Stack, Panel, Pivot, PivotItem, PrimaryButton } from '@fluentui/react'; import { Stack, Panel, PrimaryButton } from '@fluentui/react';
import { EXPERIMENT } from '../../../static/datamodel'; import { EXPERIMENT } from '../../static/datamodel';
import MonacoEditor from 'react-monaco-editor'; import MonacoEditor from 'react-monaco-editor';
import { MONACO } from '../../../static/const'; import { MONACO } from '../../static/const';
import { AppContext } from '../../../App'; import { AppContext } from '../../App';
import { convertDuration, convertTimeAsUnit } from '../../../static/function'; import { convertDuration, convertTimeAsUnit, caclMonacoEditorHeight } from '../../static/function';
import { prettyStringify } from '../../../static/json_util'; import { prettyStringify } from '../../static/json_util';
import lodash from 'lodash'; import lodash from 'lodash';
import '../../../static/style/logDrawer.scss'; import '../../static/style/logDrawer.scss';
interface LogDrawerProps { interface LogDrawerProps {
hideConfigPanel: () => void; hideConfigPanel: () => void;
activeTab?: string; panelName: string;
} }
interface LogDrawerState { interface LogDrawerState {
...@@ -19,6 +19,12 @@ interface LogDrawerState { ...@@ -19,6 +19,12 @@ interface LogDrawerState {
innerWidth: number; innerWidth: number;
} }
/**
* search space
* config
* model
*/
class TrialConfigPanel extends React.Component<LogDrawerProps, LogDrawerState> { class TrialConfigPanel extends React.Component<LogDrawerProps, LogDrawerState> {
constructor(props: LogDrawerProps) { constructor(props: LogDrawerProps) {
super(props); super(props);
...@@ -43,11 +49,9 @@ class TrialConfigPanel extends React.Component<LogDrawerProps, LogDrawerState> { ...@@ -43,11 +49,9 @@ class TrialConfigPanel extends React.Component<LogDrawerProps, LogDrawerState> {
} }
render(): React.ReactNode { render(): React.ReactNode {
const { hideConfigPanel, activeTab } = this.props; const { hideConfigPanel, panelName } = this.props;
const { panelInnerHeight, innerWidth } = this.state; const { panelInnerHeight, innerWidth } = this.state;
// [marginTop 16px] + [Search space 46px] + const monacoEditorHeight = caclMonacoEditorHeight(panelInnerHeight);
// button[height: 32px, marginTop: 45px, marginBottom: 25px] + [padding-bottom: 20px]
const monacoEditorHeight = panelInnerHeight - 184;
const blacklist = [ const blacklist = [
'id', 'id',
'logDir', 'logDir',
...@@ -83,12 +87,10 @@ class TrialConfigPanel extends React.Component<LogDrawerProps, LogDrawerState> { ...@@ -83,12 +87,10 @@ class TrialConfigPanel extends React.Component<LogDrawerProps, LogDrawerState> {
isLightDismiss={true} isLightDismiss={true}
onLightDismissClick={hideConfigPanel} onLightDismissClick={hideConfigPanel}
> >
<div className='log-tab-body'> <div className='panel'>
<Pivot {panelName === 'search space' ? (
initialSelectedKey={activeTab} <div>
style={{ minHeight: 190, paddingTop: '16px' }} <div className='panelName'>Search space</div>
>
<PivotItem headerText='Search space' itemKey='search space'>
<MonacoEditor <MonacoEditor
height={monacoEditorHeight} height={monacoEditorHeight}
language='json' language='json'
...@@ -96,22 +98,22 @@ class TrialConfigPanel extends React.Component<LogDrawerProps, LogDrawerState> { ...@@ -96,22 +98,22 @@ class TrialConfigPanel extends React.Component<LogDrawerProps, LogDrawerState> {
value={prettyStringify(EXPERIMENT.searchSpace, prettyWidth, 2)} value={prettyStringify(EXPERIMENT.searchSpace, prettyWidth, 2)}
options={MONACO} options={MONACO}
/> />
</PivotItem> </div>
<PivotItem headerText='Config' itemKey='config'> ) : (
<div className='profile'> <div className='profile'>
<MonacoEditor <div className='panelName'>Config</div>
width='100%' <MonacoEditor
height={monacoEditorHeight} width='100%'
language='json' height={monacoEditorHeight}
theme='vs-light' language='json'
value={showProfile} theme='vs-light'
options={MONACO} value={showProfile}
/> options={MONACO}
</div> />
</PivotItem> </div>
</Pivot> )}
<PrimaryButton text='Close' className='configClose' onClick={hideConfigPanel} />
</div> </div>
<PrimaryButton text='Close' className='configClose' onClick={hideConfigPanel} />
</Panel> </Panel>
</Stack> </Stack>
); );
......
...@@ -15,8 +15,8 @@ import { ...@@ -15,8 +15,8 @@ import {
import React from 'react'; import React from 'react';
import { EXPERIMENT, TRIALS } from '../../static/datamodel'; import { EXPERIMENT, TRIALS } from '../../static/datamodel';
import { TOOLTIP_BACKGROUND_COLOR } from '../../static/const'; import { TOOLTIP_BACKGROUND_COLOR } from '../../static/const';
import { convertDuration, formatTimestamp } from '../../static/function'; import { convertDuration, formatTimestamp, copyAndSort } from '../../static/function';
import { TableObj } from '../../static/interface'; import { TableObj, SortInfo } from '../../static/interface';
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';
...@@ -56,36 +56,6 @@ const searchOptionLiterals = { ...@@ -56,36 +56,6 @@ const searchOptionLiterals = {
const defaultDisplayedColumns = ['sequenceId', 'id', 'duration', 'status', 'latestAccuracy']; const defaultDisplayedColumns = ['sequenceId', 'id', 'duration', 'status', 'latestAccuracy'];
interface SortInfo {
field: string;
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 { function _inferColumnTitle(columnKey: string): string {
if (columnKey === 'sequenceId') { if (columnKey === 'sequenceId') {
return 'Trial No.'; return 'Trial No.';
...@@ -238,7 +208,7 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -238,7 +208,7 @@ class TableList extends React.Component<TableListProps, TableListState> {
const { sortInfo } = this.state; const { sortInfo } = this.state;
if (sortInfo.field !== '') { if (sortInfo.field !== '') {
return _copyAndSort(items, sortInfo.field, sortInfo.isDescend); return copyAndSort(items, sortInfo.field, sortInfo.isDescend);
} else { } else {
return items; return items;
} }
...@@ -255,6 +225,7 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -255,6 +225,7 @@ class TableList extends React.Component<TableListProps, TableListState> {
<Icon <Icon
aria-hidden={true} aria-hidden={true}
iconName='ChevronRight' iconName='ChevronRight'
className='cursor'
styles={{ styles={{
root: { root: {
transition: 'all 0.2s', transition: 'all 0.2s',
......
...@@ -262,6 +262,37 @@ function formatComplexTypeValue(value: any): string | number { ...@@ -262,6 +262,37 @@ function formatComplexTypeValue(value: any): string | number {
} }
} }
function caclMonacoEditorHeight(height): number {
// [Search space 56px] + [marginBottom 18px] +
// button[height: 32px, marginTop: 45px, marginBottom: 7px]
// panel own padding-bottom: 20px;
return height - 178;
}
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;
});
}
export { export {
convertTime, convertTime,
convertDuration, convertDuration,
...@@ -280,5 +311,7 @@ export { ...@@ -280,5 +311,7 @@ export {
isArrayType, isArrayType,
requestAxios, requestAxios,
isNaNorInfinity, isNaNorInfinity,
formatComplexTypeValue formatComplexTypeValue,
caclMonacoEditorHeight,
copyAndSort
}; };
...@@ -131,7 +131,7 @@ interface MetricDataRecord { ...@@ -131,7 +131,7 @@ interface MetricDataRecord {
} }
interface TrialJobInfo { interface TrialJobInfo {
id: string; trialJobId: string;
sequenceId: number; sequenceId: number;
status: string; status: string;
startTime?: number; startTime?: number;
...@@ -212,6 +212,12 @@ interface EventMap { ...@@ -212,6 +212,12 @@ interface EventMap {
[key: string]: () => void; [key: string]: () => void;
} }
// table column sort
interface SortInfo {
field: string;
isDescend?: boolean;
}
export { export {
TableObj, TableObj,
TableRecord, TableRecord,
...@@ -233,5 +239,6 @@ export { ...@@ -233,5 +239,6 @@ export {
NNIManagerStatus, NNIManagerStatus,
EventMap, EventMap,
SingleAxis, SingleAxis,
MultipleAxes MultipleAxes,
SortInfo
}; };
...@@ -144,9 +144,9 @@ class Trial implements TableObj { ...@@ -144,9 +144,9 @@ class Trial implements TableObj {
} }
return { return {
key: this.info.id, key: this.info.trialJobId,
sequenceId: this.info.sequenceId, sequenceId: this.info.sequenceId,
id: this.info.id, id: this.info.trialJobId,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
startTime: this.info.startTime!, startTime: this.info.startTime!,
endTime: this.info.endTime, endTime: this.info.endTime,
...@@ -169,7 +169,7 @@ class Trial implements TableObj { ...@@ -169,7 +169,7 @@ class Trial implements TableObj {
} }
get id(): string { get id(): string {
return this.info.id; return this.info.trialJobId;
} }
get duration(): number { get duration(): number {
......
...@@ -172,11 +172,11 @@ class TrialManager { ...@@ -172,11 +172,11 @@ class TrialManager {
requestAxios(`${MANAGER_IP}/trial-jobs`) requestAxios(`${MANAGER_IP}/trial-jobs`)
.then(data => { .then(data => {
for (const trialInfo of data as TrialJobInfo[]) { for (const trialInfo of data as TrialJobInfo[]) {
if (this.trials.has(trialInfo.id)) { if (this.trials.has(trialInfo.trialJobId)) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
updated = this.trials.get(trialInfo.id)!.updateTrialJobInfo(trialInfo) || updated; updated = this.trials.get(trialInfo.trialJobId)!.updateTrialJobInfo(trialInfo) || updated;
} else { } else {
this.trials.set(trialInfo.id, new Trial(trialInfo, undefined)); this.trials.set(trialInfo.trialJobId, new Trial(trialInfo, undefined));
updated = true; updated = true;
} }
this.maxSequenceId = Math.max(this.maxSequenceId, trialInfo.sequenceId); this.maxSequenceId = Math.max(this.maxSequenceId, trialInfo.sequenceId);
......
.cursor{
&:hover, & i:hover{
cursor: pointer;
}
}
\ No newline at end of file
$buttonsMarginTop: 45px;
$buttonsMarginBottom: 7px;
$navBarHeight: 56px;
$navBarMarginBottom: 18px;
.download { .download {
button, button,
button:active, button:active,
...@@ -7,22 +12,51 @@ ...@@ -7,22 +12,51 @@
} }
} }
.log-tab-body {
.refresh {
margin-left: 10px;
display: none;
}
}
/* office-fabric-ui */ /* office-fabric-ui */
.ms-Panel-main { .ms-Panel-main {
width: 55%; width: 55%;
background: #fff; background: #fff;
.ms-Panel-content {
padding: 0;
}
.ms-Pivot {
line-height: 56px;
padding: 0 38px;
background: #f2f2f2;
.ms-Pivot-linkContent:hover {
border-bottom: 2px solid #ccc;
}
.is-selected {
.ms-Pivot-linkContent:hover {
border: none;
}
}
}
}
.panel {
padding: 0 38px;
}
// for log panel editor
.logMargin {
margin-top: 20px;
}
.panelName {
line-height: $navBarHeight;
font-size: 14px;
margin-bottom: $navBarMarginBottom;
font-weight: 500;
} }
/* log drawer download & close button's row */ /* log drawer download & close button's row */
.buttons { .buttons {
margin-top: 16px; margin: $buttonsMarginTop 0 $buttonsMarginBottom 0;
.close { .close {
text-align: right; text-align: right;
...@@ -30,5 +64,5 @@ ...@@ -30,5 +64,5 @@
} }
.configClose { .configClose {
margin: 45px 0 25px 0; margin: $buttonsMarginTop 0 $buttonsMarginBottom 0;
} }
$seriesIconMargin: 8px; $seriesIconMargin: 10px;
.ExpDuration { .ExpDuration {
margin-top: 28px; margin-top: 20px;
span:hover {
cursor: pointer;
}
.maxTrialNum { .maxTrialNum {
margin-bottom: 10px; margin-bottom: 10px;
...@@ -13,7 +9,7 @@ $seriesIconMargin: 8px; ...@@ -13,7 +9,7 @@ $seriesIconMargin: 8px;
} }
.exp-progress { .exp-progress {
margin-top: 10px; margin-top: 16px;
.bold { .bold {
font-weight: 500; font-weight: 500;
...@@ -57,17 +53,17 @@ $seriesIconMargin: 8px; ...@@ -57,17 +53,17 @@ $seriesIconMargin: 8px;
} }
&-dropdown { &-dropdown {
width: 48px; width: 65px;
display: inline-block; display: inline-block;
position: relative; position: relative;
top: 13px; top: 13px;
left: 4px; left: 4px;
margin-right: 3px; margin-right: 3px;
}
}
.ExpDuration .series .confirm { .ms-Dropdown-title {
margin: 0 6px; padding-right: 0;
}
}
} }
.series { .series {
...@@ -114,4 +110,12 @@ $seriesIconMargin: 8px; ...@@ -114,4 +110,12 @@ $seriesIconMargin: 8px;
.basic p { .basic p {
margin-top: 0; margin-top: 0;
} }
p.margin {
margin-top: 20px;
}
}
.trialStatus {
margin-top: 8px;
} }
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