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

Merge pull request #3029 from liuzhe-lz/v2.0-merge

Merge master into v2.0
parents e21a6984 3b90b9d9
...@@ -3,5 +3,8 @@ export const ExpDurationContext = React.createContext({ ...@@ -3,5 +3,8 @@ export const ExpDurationContext = React.createContext({
maxExecDuration: 0, maxExecDuration: 0,
execDuration: 0, execDuration: 0,
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
updateOverviewPage: (): void => {} updateOverviewPage: (): void => {},
maxDurationUnit: 'm',
// eslint-disable-next-line @typescript-eslint/no-empty-function
changeMaxDurationUnit: (_val: 'd' | 'h' | 'm'): void => {}
}); });
import * as React from 'react'; import * as React from 'react';
import { Stack, TooltipHost, ProgressIndicator } from '@fluentui/react'; import { Stack, TooltipHost, ProgressIndicator, DirectionalHint } from '@fluentui/react';
import { EXPERIMENT, TRIALS } from '../../../static/datamodel'; import { EXPERIMENT, TRIALS } from '../../../static/datamodel';
import { CONTROLTYPE } from '../../../static/const'; import { CONTROLTYPE, TOOLTIP_BACKGROUND_COLOR, MAX_TRIAL_NUMBERS } from '../../../static/const';
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';
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 => { export const TrialCount = (): any => {
const count = TRIALS.countStatus(); const count = TRIALS.countStatus();
...@@ -30,8 +13,9 @@ export const TrialCount = (): any => { ...@@ -30,8 +13,9 @@ export const TrialCount = (): any => {
const stoppedCount = count.get('USER_CANCELED')! + count.get('SYS_CANCELED')! + count.get('EARLY_STOPPED')!; const stoppedCount = count.get('USER_CANCELED')! + count.get('SYS_CANCELED')! + count.get('EARLY_STOPPED')!;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const bar2 = count.get('RUNNING')! + count.get('SUCCEEDED')! + count.get('FAILED')! + stoppedCount; const bar2 = count.get('RUNNING')! + count.get('SUCCEEDED')! + count.get('FAILED')! + stoppedCount;
const maxTrialNum = EXPERIMENT.profile.params.maxTrialNum;
// support type [0, 1], not 98% // support type [0, 1], not 98%
const bar2Percent = bar2 / EXPERIMENT.profile.params.maxTrialNum; const bar2Percent = bar2 / maxTrialNum;
return ( return (
<ExpDurationContext.Consumer> <ExpDurationContext.Consumer>
{(value): React.ReactNode => { {(value): React.ReactNode => {
...@@ -39,40 +23,36 @@ export const TrialCount = (): any => { ...@@ -39,40 +23,36 @@ 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={itemStyles}> <div style={trialCountItem1}>
<TooltipHost content={bar2.toString()}> <TooltipHost
<ProgressIndicator percentComplete={bar2Percent} barHeight={15} /> content={bar2.toString()}
directionalHint={DirectionalHint.bottomCenter}
tooltipProps={{
calloutProps: {
styles: {
beak: { background: TOOLTIP_BACKGROUND_COLOR },
beakCurtain: { background: TOOLTIP_BACKGROUND_COLOR },
calloutMain: { background: TOOLTIP_BACKGROUND_COLOR }
}
}
}}
>
<ProgressIndicator
className={EXPERIMENT.status}
percentComplete={bar2Percent}
barHeight={15}
/>
</TooltipHost> </TooltipHost>
<Stack horizontal className='mess'> <div className='exp-progress'>
<div style={itemRunning} className='basic'> <span className={`${EXPERIMENT.status} bold`}>{bar2}</span>
<p>Running</p> <span className='joiner'>/</span>
<div>{count.get('RUNNING')}</div> <span>{maxTrialNum}</span>
</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>
<div style={itemStyle2}> <div style={trialCountItem2}>
<EditExpeParamContext.Provider <EditExpeParamContext.Provider
value={{ value={{
title: 'Max trial numbers', title: MAX_TRIAL_NUMBERS,
field: 'maxTrialNum', field: 'maxTrialNum',
editType: CONTROLTYPE[1], editType: CONTROLTYPE[1],
maxExecDuration: '', maxExecDuration: '',
...@@ -81,7 +61,9 @@ export const TrialCount = (): any => { ...@@ -81,7 +61,9 @@ export const TrialCount = (): any => {
updateOverviewPage updateOverviewPage
}} }}
> >
<EditExperimentParam /> <div className='maxTrialNum'>
<EditExperimentParam />
</div>
</EditExpeParamContext.Provider> </EditExpeParamContext.Provider>
<EditExpeParamContext.Provider <EditExpeParamContext.Provider
value={{ value={{
...@@ -99,6 +81,28 @@ export const TrialCount = (): any => { ...@@ -99,6 +81,28 @@ export const TrialCount = (): any => {
</EditExpeParamContext.Provider> </EditExpeParamContext.Provider>
</div> </div>
</Stack> </Stack>
<Stack horizontal horizontalAlign='space-between' className='mess'>
<div className='basic'>
<p>Running</p>
<div>{count.get('RUNNING')}</div>
</div>
<div className='basic'>
<p>Succeeded</p>
<div>{count.get('SUCCEEDED')}</div>
</div>
<div className='basic'>
<p>Stopped</p>
<div>{stoppedCount}</div>
</div>
<div className='basic'>
<p>Failed</p>
<div>{count.get('FAILED')}</div>
</div>
<div className='basic'>
<p>Waiting</p>
<div>{count.get('WAITING')}</div>
</div>
</Stack>
</React.Fragment> </React.Fragment>
); );
}} }}
......
const durationItem1: React.CSSProperties = {
width: '33%'
};
const durationItem2: React.CSSProperties = {
width: '52%',
paddingLeft: '15%'
};
const trialCountItem1: React.CSSProperties = {
width: '33%'
};
const trialCountItem2: React.CSSProperties = {
width: '52%'
};
export { durationItem1, durationItem2, trialCountItem1, trialCountItem2 };
...@@ -24,61 +24,63 @@ export const ReBasicInfo = (): any => { ...@@ -24,61 +24,63 @@ export const ReBasicInfo = (): any => {
return ( return (
<div> <div>
<div className='basic'> <div className='basic'>
<p>ID: {EXPERIMENT.profile.id}</p> <p>
ID: <span>{EXPERIMENT.profile.id}</span>
</p>
<div>{EXPERIMENT.profile.params.experimentName}</div> <div>{EXPERIMENT.profile.params.experimentName}</div>
</div> </div>
<div className='basic'> <div className='basic'>
<Stack className='basic'> <p>Status</p>
<p>Status</p> <Stack horizontal className='status'>
<Stack horizontal className='status'> <span className={`${EXPERIMENT.status} status-text`}>{EXPERIMENT.status}</span>
<span className={`${EXPERIMENT.status} status-text`}>{EXPERIMENT.status}</span> {EXPERIMENT.status === 'ERROR' ? (
{EXPERIMENT.status === 'ERROR' ? ( <div>
<div> <div className={styles.buttonArea} ref={ref}>
<div className={styles.buttonArea} ref={ref}> <IconButton
<IconButton iconProps={{ iconName: 'info' }}
iconProps={{ iconName: 'info' }} onClick={isCalloutVisible ? onDismiss : showCallout}
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> </div>
) : null} {isCalloutVisible && (
</Stack> <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} style={{ color: '#333' }}>
Error
</p>
</div>
<div className={styles.inner}>
<p className={styles.subtext} id={descriptionId} style={{ color: '#333' }}>
{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>
<div className='basic'> <div className='basic'>
<BestMetricContext.Consumer> <BestMetricContext.Consumer>
{(value): React.ReactNode => ( {(value): React.ReactNode => (
<Stack> <Stack className='bestMetric'>
<p>Best metric</p> <p>Best metric</p>
<div>{isNaN(value.bestAccuracy) ? 'N/A' : value.bestAccuracy.toFixed(6)}</div> <div className={EXPERIMENT.status}>
{isNaN(value.bestAccuracy) ? 'N/A' : value.bestAccuracy.toFixed(6)}
</div>
</Stack> </Stack>
)} )}
</BestMetricContext.Consumer> </BestMetricContext.Consumer>
......
...@@ -18,4 +18,10 @@ const entriesOption = [ ...@@ -18,4 +18,10 @@ const entriesOption = [
{ key: '100', text: '100' } { key: '100', text: '100' }
]; ];
export { itemStyle1, itemStyleSucceed, itemStyle2, entriesOption }; const durationUnit = [
{ key: 'm', text: 'min' },
{ key: 'h', text: 'hour' },
{ key: 'd', text: 'day' }
];
export { itemStyle1, itemStyleSucceed, itemStyle2, entriesOption, durationUnit };
...@@ -63,12 +63,10 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState> ...@@ -63,12 +63,10 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState>
} }
tooltipStr = ( tooltipStr = (
<div> <React.Fragment>
<p>The experiment is running, please wait for the final metric patiently.</p> The experiment is running, please wait for the final metric patiently. You could also find status of trial
<div className='link'> job with <span>{DETAILTABS}</span> button.
You could also find status of trial job with <span>{DETAILTABS}</span> button. </React.Fragment>
</div>
</div>
); );
columns = [ columns = [
...@@ -76,34 +74,36 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState> ...@@ -76,34 +74,36 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState>
name: 'Trial No.', name: 'Trial No.',
key: 'sequenceId', key: 'sequenceId',
fieldName: 'sequenceId', // required! fieldName: 'sequenceId', // required!
minWidth: (window.innerWidth * 0.333 - 150) / 5, minWidth: 50,
maxWidth: (window.innerWidth * 0.333 - 150) / 5, maxWidth: 87,
isResizable: true, isResizable: true,
data: 'number', data: 'number',
onColumnClick: this.onColumnClick onColumnClick: this.onColumnClick,
onRender: (item: any): React.ReactNode => <div className='succeed-padding'>{item.sequenceId}</div>
}, },
{ {
name: 'ID', name: 'ID',
key: 'id', key: 'id',
fieldName: 'id', fieldName: 'id',
minWidth: (window.innerWidth * 0.333 - 150) / 5, minWidth: 50,
maxWidth: (window.innerWidth * 0.333 - 150) / 5, maxWidth: 87,
isResizable: true, isResizable: true,
className: 'tableHead leftTitle', className: 'tableHead leftTitle',
data: 'string', data: 'string',
onColumnClick: this.onColumnClick onColumnClick: this.onColumnClick,
onRender: (item: any): React.ReactNode => <div className='succeed-padding'>{item.id}</div>
}, },
{ {
name: 'Duration', name: 'Duration',
key: 'duration', key: 'duration',
minWidth: (window.innerWidth * 0.333 - 150) / 5, minWidth: 65,
maxWidth: (window.innerWidth * 0.333 - 150) / 5, maxWidth: 150,
isResizable: true, isResizable: true,
fieldName: 'duration', fieldName: 'duration',
data: 'number', data: 'number',
onColumnClick: this.onColumnClick, onColumnClick: this.onColumnClick,
onRender: (item: any): React.ReactNode => ( onRender: (item: any): React.ReactNode => (
<div className='durationsty'> <div className='durationsty succeed-padding'>
<div>{convertDuration(item.duration)}</div> <div>{convertDuration(item.duration)}</div>
</div> </div>
) )
...@@ -111,26 +111,24 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState> ...@@ -111,26 +111,24 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState>
{ {
name: 'Status', name: 'Status',
key: 'status', key: 'status',
minWidth: (window.innerWidth * 0.333 - 150) / 5, minWidth: 80,
maxWidth: (window.innerWidth * 0.333 - 150) / 5, maxWidth: 150,
isResizable: true, isResizable: true,
fieldName: 'status', fieldName: 'status',
onRender: (item: any): React.ReactNode => { onRender: (item: any): React.ReactNode => (
return <div className={`${item.status} commonStyle`}>{item.status}</div>; <div className={`${item.status} commonStyle succeed-padding`}>{item.status}</div>
} )
}, },
{ {
name: 'Default metric', name: 'Default metric',
key: 'accuracy', key: 'accuracy',
fieldName: 'accuracy', fieldName: 'accuracy',
minWidth: (window.innerWidth * 0.333 - 200) / 5, minWidth: 100,
// maxWidth: (window.innerWidth * 0.333 - 150) / 5, maxWidth: 160,
isResizable: true, isResizable: true,
data: 'number', data: 'number',
onColumnClick: this.onColumnClick, onColumnClick: this.onColumnClick,
onRender: (item: any): React.ReactNode => { onRender: (item: any): React.ReactNode => <DefaultMetric trialId={item.id} />
return <DefaultMetric trialId={item.id} />;
}
} }
]; ];
...@@ -155,6 +153,7 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState> ...@@ -155,6 +153,7 @@ 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
......
...@@ -13,7 +13,7 @@ class DefaultMetric extends React.Component<DefaultMetricProps, {}> { ...@@ -13,7 +13,7 @@ class DefaultMetric extends React.Component<DefaultMetricProps, {}> {
render(): React.ReactNode { render(): React.ReactNode {
const accuracy = TRIALS.getTrial(this.props.trialId).accuracy; const accuracy = TRIALS.getTrial(this.props.trialId).accuracy;
return <div>{accuracy !== undefined ? formatAccuracy(accuracy) : '--'}</div>; return <div className='succeed-padding'>{accuracy !== undefined ? formatAccuracy(accuracy) : '--'}</div>;
} }
} }
......
...@@ -18,8 +18,8 @@ export const TrialConfigButton = (): any => { ...@@ -18,8 +18,8 @@ export const TrialConfigButton = (): any => {
return ( return (
<React.Fragment> <React.Fragment>
<Stack className='config'> <Stack className='config'>
<DefaultButton text='Config' onClick={showTrialConfigpPanel} />
<DefaultButton text='Search space' onClick={showSearchSpacePanel} /> <DefaultButton text='Search space' onClick={showSearchSpacePanel} />
<DefaultButton text='Config' onClick={showTrialConfigpPanel} />
</Stack> </Stack>
{isShowConfigPanel && <TrialConfigPanel hideConfigPanel={hideConfigPanel} activeTab={activeTab} />} {isShowConfigPanel && <TrialConfigPanel hideConfigPanel={hideConfigPanel} activeTab={activeTab} />}
</React.Fragment> </React.Fragment>
......
...@@ -3,7 +3,8 @@ import { Stack, Panel, Pivot, PivotItem, PrimaryButton } from '@fluentui/react'; ...@@ -3,7 +3,8 @@ import { Stack, Panel, Pivot, PivotItem, 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 { convertDuration } from '../../../static/function'; import { AppContext } from '../../../App';
import { convertDuration, convertTimeAsUnit } 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';
...@@ -15,6 +16,7 @@ interface LogDrawerProps { ...@@ -15,6 +16,7 @@ interface LogDrawerProps {
interface LogDrawerState { interface LogDrawerState {
panelInnerHeight: number; panelInnerHeight: number;
innerWidth: number;
} }
class TrialConfigPanel extends React.Component<LogDrawerProps, LogDrawerState> { class TrialConfigPanel extends React.Component<LogDrawerProps, LogDrawerState> {
...@@ -22,13 +24,15 @@ class TrialConfigPanel extends React.Component<LogDrawerProps, LogDrawerState> { ...@@ -22,13 +24,15 @@ class TrialConfigPanel extends React.Component<LogDrawerProps, LogDrawerState> {
super(props); super(props);
this.state = { this.state = {
panelInnerHeight: window.innerHeight panelInnerHeight: window.innerHeight,
innerWidth: window.innerWidth
}; };
} }
setLogDrawerHeight(): void { // use arrow function for change window size met error: this.setState is not a function
this.setState(() => ({ panelInnerHeight: window.innerHeight })); setLogDrawerHeight = (): void => {
} this.setState(() => ({ panelInnerHeight: window.innerHeight, innerWidth: window.innerWidth }));
};
async componentDidMount(): Promise<void> { async componentDidMount(): Promise<void> {
window.addEventListener('resize', this.setLogDrawerHeight); window.addEventListener('resize', this.setLogDrawerHeight);
...@@ -40,7 +44,7 @@ class TrialConfigPanel extends React.Component<LogDrawerProps, LogDrawerState> { ...@@ -40,7 +44,7 @@ class TrialConfigPanel extends React.Component<LogDrawerProps, LogDrawerState> {
render(): React.ReactNode { render(): React.ReactNode {
const { hideConfigPanel, activeTab } = this.props; const { hideConfigPanel, activeTab } = this.props;
const { panelInnerHeight } = this.state; const { panelInnerHeight, innerWidth } = this.state;
// [marginTop 16px] + [Search space 46px] + // [marginTop 16px] + [Search space 46px] +
// button[height: 32px, marginTop: 45px, marginBottom: 25px] + [padding-bottom: 20px] // button[height: 32px, marginTop: 45px, marginBottom: 25px] + [padding-bottom: 20px]
const monacoEditorHeight = panelInnerHeight - 184; const monacoEditorHeight = panelInnerHeight - 184;
...@@ -58,45 +62,61 @@ class TrialConfigPanel extends React.Component<LogDrawerProps, LogDrawerState> { ...@@ -58,45 +62,61 @@ class TrialConfigPanel extends React.Component<LogDrawerProps, LogDrawerState> {
}; };
const profile = lodash.cloneDeep(EXPERIMENT.profile); const profile = lodash.cloneDeep(EXPERIMENT.profile);
profile.execDuration = convertDuration(profile.execDuration); profile.execDuration = convertDuration(profile.execDuration);
profile.params.maxExecDuration = convertDuration(profile.params.maxExecDuration);
const showProfile = JSON.stringify(profile, filter, 2); const prettyWidth = innerWidth > 1400 ? 100 : 60;
return ( return (
<Stack> <AppContext.Consumer>
<Panel {(value): React.ReactNode => {
isOpen={true} const unit = value.maxDurationUnit;
hasCloseButton={false} profile.params.maxExecDuration = `${convertTimeAsUnit(
isFooterAtBottom={true} unit,
isLightDismiss={true} profile.params.maxExecDuration
onLightDismissClick={hideConfigPanel} )}${unit}`;
> const showProfile = JSON.stringify(profile, filter, 2);
<div className='log-tab-body'> return (
<Pivot initialSelectedKey={activeTab} style={{ minHeight: 190, paddingTop: '16px' }}> <Stack>
<PivotItem headerText='Search space' itemKey='search space'> <Panel
<MonacoEditor isOpen={true}
height={monacoEditorHeight} hasCloseButton={false}
language='json' isFooterAtBottom={true}
theme='vs-light' isLightDismiss={true}
value={prettyStringify(EXPERIMENT.searchSpace, 300, 2)} onLightDismissClick={hideConfigPanel}
options={MONACO} >
/> <div className='log-tab-body'>
</PivotItem> <Pivot
<PivotItem headerText='Config' itemKey='config'> initialSelectedKey={activeTab}
<div className='profile'> style={{ minHeight: 190, paddingTop: '16px' }}
<MonacoEditor >
width='100%' <PivotItem headerText='Search space' itemKey='search space'>
height={monacoEditorHeight} <MonacoEditor
language='json' height={monacoEditorHeight}
theme='vs-light' language='json'
value={showProfile} theme='vs-light'
options={MONACO} value={prettyStringify(EXPERIMENT.searchSpace, prettyWidth, 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> </div>
</PivotItem> <PrimaryButton text='Close' className='configClose' onClick={hideConfigPanel} />
</Pivot> </Panel>
</div> </Stack>
<PrimaryButton text='Close' className='configClose' onClick={hideConfigPanel} /> );
</Panel> }}
</Stack> </AppContext.Consumer>
); );
} }
} }
......
...@@ -9,10 +9,12 @@ import { ...@@ -9,10 +9,12 @@ import {
SelectionMode, SelectionMode,
Stack, Stack,
StackItem, StackItem,
TooltipHost TooltipHost,
DirectionalHint
} from '@fluentui/react'; } from '@fluentui/react';
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 { convertDuration, formatTimestamp } from '../../static/function'; import { convertDuration, formatTimestamp } from '../../static/function';
import { TableObj } from '../../static/interface'; import { TableObj } from '../../static/interface';
import '../../static/style/search.scss'; import '../../static/style/search.scss';
...@@ -26,6 +28,7 @@ import '../../static/style/pagination.scss'; ...@@ -26,6 +28,7 @@ import '../../static/style/pagination.scss';
import '../../static/style/search.scss'; import '../../static/style/search.scss';
import '../../static/style/table.scss'; import '../../static/style/table.scss';
import '../../static/style/tableStatus.css'; import '../../static/style/tableStatus.css';
import '../../static/style/overview/overviewTitle.scss';
import { blocked, copy, LineChart, tableListIcon } from '../buttons/Icon'; import { blocked, copy, LineChart, tableListIcon } from '../buttons/Icon';
import ChangeColumnComponent from '../modals/ChangeColumnComponent'; import ChangeColumnComponent from '../modals/ChangeColumnComponent';
import Compare from '../modals/Compare'; import Compare from '../modals/Compare';
...@@ -247,7 +250,7 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -247,7 +250,7 @@ class TableList extends React.Component<TableListProps, TableListState> {
{ {
key: '_expand', key: '_expand',
name: '', name: '',
onRender: (item, index): any => { onRender: (item): any => {
return ( return (
<Icon <Icon
aria-hidden={true} aria-hidden={true}
...@@ -267,8 +270,9 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -267,8 +270,9 @@ class TableList extends React.Component<TableListProps, TableListState> {
} else { } else {
this._expandedTrialIds.delete(newItem.id); this._expandedTrialIds.delete(newItem.id);
} }
const newItems = [...this.state.displayedItems]; const newItems = this.state.displayedItems.map(item =>
newItems[index as number] = newItem; item.id === newItem.id ? newItem : item
);
this.setState({ this.setState({
displayedItems: newItems displayedItems: newItems
}); });
...@@ -294,17 +298,16 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -294,17 +298,16 @@ class TableList extends React.Component<TableListProps, TableListState> {
// FIXME: default metric is hacked as latestAccuracy currently // FIXME: default metric is hacked as latestAccuracy currently
continue; continue;
} }
const lengths = tableItems.map(item => `${item[k]}`.length);
const avgLengths = lengths.reduce((a, b) => a + b) / lengths.length;
const columnTitle = _inferColumnTitle(k); const columnTitle = _inferColumnTitle(k);
const columnWidth = Math.max(columnTitle.length, avgLengths);
// TODO: add blacklist // TODO: add blacklist
// 0.85: tableWidth / screen
const widths = window.innerWidth * 0.85;
columns.push({ columns.push({
name: columnTitle, name: columnTitle,
key: k, key: k,
fieldName: k, fieldName: k,
minWidth: columnWidth * 13, minWidth: widths * 0.12,
maxWidth: columnWidth * 18, maxWidth: widths * 0.19,
isResizable: true, isResizable: true,
onColumnClick: this._onColumnClick.bind(this), onColumnClick: this._onColumnClick.bind(this),
...(k === 'status' && { ...(k === 'status' && {
...@@ -316,7 +319,19 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -316,7 +319,19 @@ class TableList extends React.Component<TableListProps, TableListState> {
...((k.startsWith('metric/') || k.startsWith('space/')) && { ...((k.startsWith('metric/') || k.startsWith('space/')) && {
// show tooltip // show tooltip
onRender: (record): React.ReactNode => ( onRender: (record): React.ReactNode => (
<TooltipHost content={record[k]}> <TooltipHost
content={record[k]}
directionalHint={DirectionalHint.bottomCenter}
tooltipProps={{
calloutProps: {
styles: {
beak: { background: TOOLTIP_BACKGROUND_COLOR },
beakCurtain: { background: TOOLTIP_BACKGROUND_COLOR },
calloutMain: { background: TOOLTIP_BACKGROUND_COLOR }
}
}
}}
>
<div className='ellipsis'>{record[k]}</div> <div className='ellipsis'>{record[k]}</div>
</TooltipHost> </TooltipHost>
) )
...@@ -324,7 +339,19 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -324,7 +339,19 @@ class TableList extends React.Component<TableListProps, TableListState> {
...(k === 'latestAccuracy' && { ...(k === 'latestAccuracy' && {
// FIXME: this is ad-hoc // FIXME: this is ad-hoc
onRender: (record): React.ReactNode => ( onRender: (record): React.ReactNode => (
<TooltipHost content={record._formattedLatestAccuracy}> <TooltipHost
content={record._formattedLatestAccuracy}
directionalHint={DirectionalHint.bottomCenter}
tooltipProps={{
calloutProps: {
styles: {
beak: { background: TOOLTIP_BACKGROUND_COLOR },
beakCurtain: { background: TOOLTIP_BACKGROUND_COLOR },
calloutMain: { background: TOOLTIP_BACKGROUND_COLOR }
}
}
}}
>
<div className='ellipsis'>{record._formattedLatestAccuracy}</div> <div className='ellipsis'>{record._formattedLatestAccuracy}</div>
</TooltipHost> </TooltipHost>
) )
...@@ -344,8 +371,8 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -344,8 +371,8 @@ class TableList extends React.Component<TableListProps, TableListState> {
name: 'Operation', name: 'Operation',
key: '_operation', key: '_operation',
fieldName: 'operation', fieldName: 'operation',
minWidth: 160, minWidth: 150,
maxWidth: 200, maxWidth: 160,
isResizable: true, isResizable: true,
className: 'detail-table', className: 'detail-table',
onRender: this._renderOperationColumn.bind(this) onRender: this._renderOperationColumn.bind(this)
......
import React, { lazy, Suspense } from 'react'; import React, { lazy, Suspense } from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import App from './App'; import App from './App';
import { BrowserRouter as Router, Route, Switch, Redirect } from 'react-router-dom'; import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
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';
...@@ -19,9 +19,9 @@ ReactDOM.render( ...@@ -19,9 +19,9 @@ ReactDOM.render(
</div> </div>
} }
> >
<Route path='/' component={Overview} exact />
<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' }} />} />
</Suspense> </Suspense>
</Switch> </Switch>
</App> </App>
......
...@@ -57,6 +57,9 @@ const SUPPORTED_SEARCH_SPACE_TYPE = [ ...@@ -57,6 +57,9 @@ const SUPPORTED_SEARCH_SPACE_TYPE = [
'qlognormal' 'qlognormal'
]; ];
const TOOLTIP_BACKGROUND_COLOR = '#484848';
const MAX_TRIAL_NUMBERS = 'Max trial No.';
export { export {
MANAGER_IP, MANAGER_IP,
DOWNLOAD_IP, DOWNLOAD_IP,
...@@ -71,5 +74,7 @@ export { ...@@ -71,5 +74,7 @@ export {
METRIC_GROUP_UPDATE_THRESHOLD, METRIC_GROUP_UPDATE_THRESHOLD,
METRIC_GROUP_UPDATE_SIZE, METRIC_GROUP_UPDATE_SIZE,
CONCURRENCYTOOLTIP, CONCURRENCYTOOLTIP,
SUPPORTED_SEARCH_SPACE_TYPE SUPPORTED_SEARCH_SPACE_TYPE,
TOOLTIP_BACKGROUND_COLOR,
MAX_TRIAL_NUMBERS
}; };
...@@ -29,27 +29,6 @@ const convertTime = (num: number): string => { ...@@ -29,27 +29,6 @@ const convertTime = (num: number): string => {
} }
}; };
const convertTimeToSecond = (str: string): number => {
let seconds = 0;
let d, h, m;
if (str.includes('d')) {
[d, str] = str.split('d');
seconds += parseInt(d) * 24 * 3600;
}
if (str.includes('h')) {
[h, str] = str.split('h');
seconds += parseInt(h) * 3600;
}
if (str.includes('m')) {
[m, str] = str.split('m');
seconds += parseInt(m) * 60;
}
if (str) {
seconds += parseInt(str.split('s')[0]);
}
return seconds;
};
// trial's duration, accurate to seconds for example 10min 30s // trial's duration, accurate to seconds for example 10min 30s
const convertDuration = (seconds: number): string => { const convertDuration = (seconds: number): string => {
let str = ''; let str = '';
...@@ -78,6 +57,18 @@ const convertDuration = (seconds: number): string => { ...@@ -78,6 +57,18 @@ const convertDuration = (seconds: number): string => {
return str ? str : '0s'; return str ? str : '0s';
}; };
// according the unit(d,h,m) to convert duration
function convertTimeAsUnit(unit: string, value: number): number {
let divisor = 1;
if (unit === 'h') {
divisor = 3600;
} else if (unit === 'm') {
divisor = 60;
} else {
divisor = 24 * 3600;
}
return value / divisor;
}
function parseMetrics(metricData: string): any { function parseMetrics(metricData: string): any {
if (metricData.includes('NaN') || metricData.includes('Infinity')) { if (metricData.includes('NaN') || metricData.includes('Infinity')) {
return JSON5.parse(JSON5.parse(metricData)); return JSON5.parse(JSON5.parse(metricData));
...@@ -274,7 +265,7 @@ function formatComplexTypeValue(value: any): string | number { ...@@ -274,7 +265,7 @@ function formatComplexTypeValue(value: any): string | number {
export { export {
convertTime, convertTime,
convertDuration, convertDuration,
convertTimeToSecond, convertTimeAsUnit,
getFinalResult, getFinalResult,
getFinal, getFinal,
downFile, downFile,
......
...@@ -184,10 +184,14 @@ interface ExperimentParams { ...@@ -184,10 +184,14 @@ interface ExperimentParams {
}; };
clusterMetaData?: { clusterMetaData?: {
key: string; key: string;
value: string; value: string | ClusterItem;
}[]; }[];
} }
interface ClusterItem {
command?: string;
}
interface ExperimentProfile { interface ExperimentProfile {
params: ExperimentParams; params: ExperimentParams;
id: string; id: string;
......
.command { .overviewCommand1,
width: 100%; .overviewCommand2 {
.command {
>div { margin-top: 0;
display: inline-block; font-weight: normal;
}
.command1 {
width: 46%;
p {
margin-top: 0;
}
}
.command2 {
width: 54%;
p {
margin-top: 0;
}
} }
}
.command1, .basic {
.command2 { .lineMargin {
.lineMargin { margin-top: 20px;
margin-top: 20px; font-weight: normal;
}
} }
} }
...@@ -4,15 +4,21 @@ ...@@ -4,15 +4,21 @@
z-index: 1000; z-index: 1000;
.ms-Button--default { .ms-Button--default {
padding: 0; padding: 0 8px;
margin: 0 0 15px 0; margin: 0 0 12px 0;
border: none;
box-shadow: 0 3px 3px rgba(0, 0, 0, 0.08);
border-radius: 18px 0 0 18px; border-radius: 18px 0 0 18px;
border: 1px solid #ccc; font-size: 12px;
color: #0573bc; text-align: left;
.ms-Button-label {
font-weight: normal;
}
} }
.ms-Button--default:hover { .ms-Button--default:hover {
background-color: orangered; background-color: #0071bc;
color: #fff; color: #fff;
} }
} }
......
$seriesIconMargin: 13px; $seriesIconMargin: 8px;
.ExpDuration { .ExpDuration {
margin-top: 28px; margin-top: 28px;
...@@ -6,23 +6,74 @@ $seriesIconMargin: 13px; ...@@ -6,23 +6,74 @@ $seriesIconMargin: 13px;
span:hover { span:hover {
cursor: pointer; cursor: pointer;
} }
.maxTrialNum {
margin-bottom: 10px;
}
} }
.durationInput { .exp-progress {
width: 42px; margin-top: 10px;
height: 32px;
padding-right: 5px; .bold {
text-align: right; font-weight: 500;
outline: none; }
border: none;
border-bottom: 1px solid #ccc; .joiner {
padding: 0 3px;
}
}
.maxTrialNum {
.editparam {
position: relative;
top: -7px;
}
}
.noEditDuration {
position: relative;
top: -7px;
}
.editDuration {
position: relative;
top: -17px;
} }
.maxExecDuration { .editparam {
width: 74px; &-Input {
width: 42px;
height: 32px;
padding-right: 5px;
text-align: right;
outline: none;
border: none;
border-bottom: 1px solid #ccc;
}
.maxExecDuration {
width: 36px;
}
&-dropdown {
width: 48px;
display: inline-block;
position: relative;
top: 13px;
left: 4px;
margin-right: 3px;
}
}
.ExpDuration .series .confirm {
margin: 0 6px;
} }
.series { .series {
position: relative;
top: 5px;
i { i {
font-size: 20px; font-size: 20px;
font-weight: 700; font-weight: 700;
...@@ -39,6 +90,7 @@ $seriesIconMargin: 13px; ...@@ -39,6 +90,7 @@ $seriesIconMargin: 13px;
.cancel i { .cancel i {
color: red; color: red;
font-size: 16px;
} }
.edit i { .edit i {
...@@ -55,3 +107,11 @@ $seriesIconMargin: 13px; ...@@ -55,3 +107,11 @@ $seriesIconMargin: 13px;
z-index: 999; z-index: 999;
left: 0; left: 0;
} }
.mess {
margin-top: 20px;
.basic p {
margin-top: 0;
}
}
$boxPadding: 20px 42px; $boxPadding: 20px;
$boxBorderRadius: 5px;
$boxGapPadding: 10px;
.wrapper { .wrapper {
display: grid; display: grid;
grid-template-columns: repeat(8, 1fr); grid-template-columns: repeat(9, 1fr);
grid-auto-rows: 82px; grid-auto-rows: 97px;
grid-gap: 18px;
>div { > div {
background: #fff; background: #fff;
padding: $boxPadding; padding: $boxPadding;
border-radius: 20px; border-radius: $boxBorderRadius;
box-sizing: border-box; box-sizing: border-box;
} }
.overviewProgress { .overviewProgress {
grid-column: 2 / 5; grid-column: 2 / 6;
grid-row: 1 / 5; grid-row: 1 / 5;
display: grid; display: grid;
grid-auto-rows: 70px; grid-auto-rows: 70px;
margin: 0 $boxGapPadding;
padding: 0; padding: 0;
background: transparent; background: transparent;
...@@ -25,7 +27,7 @@ $boxPadding: 20px 42px; ...@@ -25,7 +27,7 @@ $boxPadding: 20px 42px;
.trialCount { .trialCount {
background: #fff; background: #fff;
padding: $boxPadding; padding: $boxPadding;
border-radius: 20px; border-radius: $boxBorderRadius;
box-sizing: border-box; box-sizing: border-box;
/* for alert message tooltip position */ /* for alert message tooltip position */
...@@ -34,14 +36,31 @@ $boxPadding: 20px 42px; ...@@ -34,14 +36,31 @@ $boxPadding: 20px 42px;
.duration { .duration {
// grid-row: 1 / 3; // grid-row: 1 / 3;
height: 136px; height: 139px;
} }
.trialCount { .trialCount {
margin-top: 14px; margin-top: 79px;
height: 228px; height: 239px;
} }
} }
.overviewCommand1,
.overviewCommand2 {
border-radius: 0;
}
.overviewCommand1 {
grid-column-start: 1;
border-radius: $boxBorderRadius 0 0 $boxBorderRadius;
}
.overviewCommand2 {
grid-column: 2 / 6;
margin-right: 10px;
padding-left: 30px;
border-radius: 0 $boxBorderRadius $boxBorderRadius 0;
}
} }
.overviewBasicInfo { .overviewBasicInfo {
...@@ -58,10 +77,15 @@ $boxPadding: 20px 42px; ...@@ -58,10 +77,15 @@ $boxPadding: 20px 42px;
font-size: 14px; font-size: 14px;
color: #8f8f8f; color: #8f8f8f;
margin-top: 20px; margin-top: 20px;
span {
color: #484848;
}
} }
div { div {
font-size: 16px; font-size: 16px;
font-weight: 500;
color: #484848; color: #484848;
} }
...@@ -73,8 +97,8 @@ $boxPadding: 20px 42px; ...@@ -73,8 +97,8 @@ $boxPadding: 20px 42px;
} }
.overviewTable { .overviewTable {
grid-column: 5 / 9; grid-column: 6 / 10;
grid-row: 1 / 10; grid-row: 1 / 11;
overflow: hidden; overflow: hidden;
.topTrialTitle { .topTrialTitle {
...@@ -82,7 +106,7 @@ $boxPadding: 20px 42px; ...@@ -82,7 +106,7 @@ $boxPadding: 20px 42px;
} }
.active { .active {
background: #f3f2f1; background: #d2d0ce;
} }
.max { .max {
...@@ -99,21 +123,21 @@ $boxPadding: 20px 42px; ...@@ -99,21 +123,21 @@ $boxPadding: 20px 42px;
} }
} }
.overviewCommand, .overviewCommand1,
.overviewChart { .overviewCommand2 {
grid-column: 1 / 5;
}
.overviewCommand {
height: 144px; height: 144px;
overflow: hidden; overflow: hidden;
margin-top: 10px;
} }
$circle: 10px; $circle: 10px;
$bgblue: #0071bc; $bgblue: #0071bc;
.overviewChart { .overviewChart {
grid-column: 1 / 6;
grid-row: 7 / 11; grid-row: 7 / 11;
margin-top: -38px; margin-right: $boxGapPadding;
margin-top: -29px;
.circle { .circle {
width: $circle; width: $circle;
...@@ -124,3 +148,9 @@ $bgblue: #0071bc; ...@@ -124,3 +148,9 @@ $bgblue: #0071bc;
margin-right: 18px; margin-right: 18px;
} }
} }
.showMess {
position: absolute;
top: 42%;
left: 48%;
}
$iconPaddingVal: 20px; $iconPaddingVal: 20px;
.panelTitle { .panelTitle {
img {
height: 23px;
/* (38 - 22 ) / 2 */
margin-top: 8px;
/* icon right */
padding: 0 $iconPaddingVal 0 0;
}
span { span {
font-size: 18px; font-size: 18px;
font-weight: 600; font-weight: 600;
......
/* status: 'INITIALIZED' | 'RUNNING' | 'ERROR' | 'STOPPING' | 'STOPPED' | 'DONE' */ /* status: 'INITIALIZED' | 'RUNNING' | 'ERROR' | 'STOPPING' | 'STOPPED' | 'DONE' */
$running: #0071bc;
$done: #00ad56;
$error: #a4262c;
/* status: 'TUNER_NO_MORE_TRIAL' | 'NO_MORE_TRIAL' */ /* status: 'TUNER_NO_MORE_TRIAL' | 'NO_MORE_TRIAL' */
.RUNNING, .RUNNING,
...@@ -7,27 +10,46 @@ ...@@ -7,27 +10,46 @@
.NO_MORE_TRIAL, .NO_MORE_TRIAL,
.TUNER_NO_MORE_TRIAL { .TUNER_NO_MORE_TRIAL {
/* specific status color */ /* specific status color */
color: #0071bc; color: $running;
/* progress- duration & trial numbers span */ /* progress- duration & trial numbers span */
.ms-ProgressIndicator-progressBar { .ms-ProgressIndicator-progressBar {
background-color: #0071bc; background-color: $running;
} }
} }
.DONE, .DONE,
.STOPPED { .STOPPED {
color: #009245; color: $done;
.ms-ProgressIndicator-progressBar { .ms-ProgressIndicator-progressBar {
background-color: #009245; background-color: $done;
} }
} }
.ERROR { .ERROR {
color: #eb0716; color: $error;
.ms-ProgressIndicator-progressBar { .ms-ProgressIndicator-progressBar {
background-color: #eb0716; background-color: $error;
}
}
.bestMetric {
.DONE,
.STOPPED {
color: $done;
}
.ERROR {
color: $error;
}
.RUNNING,
.STOPPING,
.INITIALIZED,
.NO_MORE_TRIAL,
.TUNER_NO_MORE_TRIAL {
color: $running;
} }
} }
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