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
...@@ -101,9 +101,9 @@ jobs: ...@@ -101,9 +101,9 @@ jobs:
displayName: 'Simple test' displayName: 'Simple test'
- job: 'macos_1015_python37' - job: 'macos_latest_python37'
pool: pool:
vmImage: 'macOS-10.15' vmImage: 'macOS-latest'
steps: steps:
- script: | - script: |
......
...@@ -573,7 +573,7 @@ def view_inshape(module_masks, mask, shape): ...@@ -573,7 +573,7 @@ def view_inshape(module_masks, mask, shape):
step_size = shape['in_shape'][2] * shape['in_shape'][3] step_size = shape['in_shape'][2] * shape['in_shape'][3]
for loc in mask.mask_index[1]: for loc in mask.mask_index[1]:
index.extend([loc * step_size + i for i in range(step_size)]) index.extend([loc * step_size + i for i in range(step_size)])
output_cmask.add_index_mask(dim=1, index=torch.tensor(index)) # pylint: disable=not-callable output_cmask.add_index_mask(dim=1, index=torch.tensor(index).to(mask.mask_index[1].device)) # pylint: disable=not-callable
module_masks.set_output_mask(output_cmask) module_masks.set_output_mask(output_cmask)
return output_cmask return output_cmask
...@@ -609,7 +609,7 @@ def view_outshape(module_masks, mask, shape): ...@@ -609,7 +609,7 @@ def view_outshape(module_masks, mask, shape):
step_size = shape['in_shape'][2] * shape['in_shape'][3] step_size = shape['in_shape'][2] * shape['in_shape'][3]
for loc in mask.mask_index[1]: for loc in mask.mask_index[1]:
index.extend([loc * step_size + i for i in range(step_size)]) index.extend([loc * step_size + i for i in range(step_size)])
input_cmask.add_index_mask(dim=1, index=torch.tensor(index)) # pylint: disable=not-callable input_cmask.add_index_mask(dim=1, index=torch.tensor(index).to(mask.mask_index[1].device)) # pylint: disable=not-callable
module_masks.set_input_mask(input_cmask) module_masks.set_input_mask(input_cmask)
return input_cmask return input_cmask
...@@ -870,7 +870,7 @@ def conv2d_mask(module_masks, mask): ...@@ -870,7 +870,7 @@ def conv2d_mask(module_masks, mask):
if index is None: if index is None:
return None, None, None return None, None, None
else: else:
index = torch.LongTensor(index).to(weight_mask.device) index = index.long().to(weight_mask.device)
weight_cmask = CoarseMask(num_dim=4) weight_cmask = CoarseMask(num_dim=4)
weight_cmask.add_index_mask(dim=dim, index=index) weight_cmask.add_index_mask(dim=dim, index=index)
bias_cmask = None bias_cmask = None
......
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
"@typescript-eslint/no-inferrable-types": 0, "@typescript-eslint/no-inferrable-types": 0,
"@typescript-eslint/no-use-before-define": [2, "nofunc"], "@typescript-eslint/no-use-before-define": [2, "nofunc"],
"@typescript-eslint/no-var-requires": 0, "@typescript-eslint/no-var-requires": 0,
"@typescript-eslint/no-unused-vars": [2, { "argsIgnorePattern": "^_" }],
"arrow-parens": [2, "as-needed"], "arrow-parens": [2, "as-needed"],
"no-inner-declarations": 0, "no-inner-declarations": 0,
"no-empty": 2, "no-empty": 2,
......
...@@ -26,9 +26,9 @@ ...@@ -26,9 +26,9 @@
} }
.content { .content {
width: 89%; width: 87%;
min-width: 1024px;
margin: 0 auto; margin: 0 auto;
min-width: 1200px;
margin-top: 74px; margin-top: 74px;
margin-bottom: 30px; margin-bottom: 30px;
} }
......
...@@ -4,6 +4,7 @@ import { COLUMN } from './static/const'; ...@@ -4,6 +4,7 @@ import { COLUMN } from './static/const';
import { EXPERIMENT, TRIALS } from './static/datamodel'; import { EXPERIMENT, TRIALS } from './static/datamodel';
import NavCon from './components/NavCon'; import NavCon from './components/NavCon';
import MessageInfo from './components/modals/MessageInfo'; import MessageInfo from './components/modals/MessageInfo';
import { TrialConfigButton } from './components/public-child/config/TrialConfigButton';
import './App.scss'; import './App.scss';
interface AppState { interface AppState {
...@@ -30,12 +31,13 @@ export const AppContext = React.createContext({ ...@@ -30,12 +31,13 @@ export const AppContext = React.createContext({
// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
changeMetricGraphMode: (val: 'max' | 'min') => {}, changeMetricGraphMode: (val: 'max' | 'min') => {},
// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
changeEntries: (val: string) => {} changeEntries: (val: string) => {},
// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
updateOverviewPage: () => {}
}); });
class App extends React.Component<{}, AppState> { class App extends React.Component<{}, AppState> {
private timerId!: number | undefined; private timerId!: number | undefined;
private dataFormatimer!: number;
private firstLoad: boolean = false; // when click refresh selector options private firstLoad: boolean = false; // when click refresh selector options
constructor(props: {}) { constructor(props: {}) {
super(props); super(props);
...@@ -60,35 +62,8 @@ class App extends React.Component<{}, AppState> { ...@@ -60,35 +62,8 @@ class App extends React.Component<{}, AppState> {
metricGraphMode: EXPERIMENT.optimizeMode === 'minimize' ? 'min' : 'max' metricGraphMode: EXPERIMENT.optimizeMode === 'minimize' ? 'min' : 'max'
})); }));
this.timerId = window.setTimeout(this.refresh, this.state.interval * 100); this.timerId = window.setTimeout(this.refresh, this.state.interval * 100);
// final result is legal
// get a succeed trial,see final result data's format
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.dataFormatimer = window.setInterval(this.getFinalDataFormat, this.state.interval * 1000);
} }
getFinalDataFormat = (): void => {
for (let i = 0; this.state.isillegalFinal === false; i++) {
if (TRIALS.succeededTrials()[0] !== undefined && TRIALS.succeededTrials()[0].final !== undefined) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const oneSucceedTrial = JSON.parse(JSON.parse(TRIALS.succeededTrials()[0].final!.data));
if (typeof oneSucceedTrial === 'number' || oneSucceedTrial.hasOwnProperty('default')) {
window.clearInterval(this.dataFormatimer);
break;
} else {
// illegal final data
this.setState(() => ({
isillegalFinal: true,
expWarningMessage:
'WebUI support final result as number and dictornary includes default keys, your experiment final result is illegal, please check your data.'
}));
window.clearInterval(this.dataFormatimer);
}
} else {
break;
}
}
};
changeInterval = (interval: number): void => { changeInterval = (interval: number): void => {
window.clearTimeout(this.timerId); window.clearTimeout(this.timerId);
if (interval === 0) { if (interval === 0) {
...@@ -116,6 +91,12 @@ class App extends React.Component<{}, AppState> { ...@@ -116,6 +91,12 @@ class App extends React.Component<{}, AppState> {
this.setState({ bestTrialEntries: entries }); this.setState({ bestTrialEntries: entries });
}; };
updateOverviewPage = (): void => {
this.setState(state => ({
experimentUpdateBroadcast: state.experimentUpdateBroadcast + 1
}));
};
shouldComponentUpdate(nextProps: any, nextState: AppState): boolean { shouldComponentUpdate(nextProps: any, nextState: AppState): boolean {
if (!(nextState.isUpdate || nextState.isUpdate === undefined)) { if (!(nextState.isUpdate || nextState.isUpdate === undefined)) {
nextState.isUpdate = true; nextState.isUpdate = true;
...@@ -155,6 +136,8 @@ class App extends React.Component<{}, AppState> { ...@@ -155,6 +136,8 @@ class App extends React.Component<{}, AppState> {
</div> </div>
<Stack className='contentBox'> <Stack className='contentBox'>
<Stack className='content'> <Stack className='content'>
{/* search space & config */}
<TrialConfigButton />
{/* if api has error field, show error message */} {/* if api has error field, show error message */}
{errorList.map( {errorList.map(
(item, key) => (item, key) =>
...@@ -179,7 +162,8 @@ class App extends React.Component<{}, AppState> { ...@@ -179,7 +162,8 @@ class App extends React.Component<{}, AppState> {
metricGraphMode, metricGraphMode,
changeMetricGraphMode: this.changeMetricGraphMode, changeMetricGraphMode: this.changeMetricGraphMode,
bestTrialEntries, bestTrialEntries,
changeEntries: this.changeEntries changeEntries: this.changeEntries,
updateOverviewPage: this.updateOverviewPage
}} }}
> >
{this.props.children} {this.props.children}
......
import * as React from 'react'; import * as React from 'react';
import { Stack, IStackTokens, Dropdown } from '@fluentui/react'; import { Stack, Icon, Dropdown, DefaultButton } from '@fluentui/react';
import { EXPERIMENT, TRIALS } from '../static/datamodel'; import { EXPERIMENT, TRIALS } from '../static/datamodel';
import { Trial } from '../static/model/trial'; import { Trial } from '../static/model/trial';
import { AppContext } from '../App'; import { AppContext } from '../App';
import { Title1 } from './overview/Title1'; import { Title } from './overview/Title';
import SuccessTable from './overview/SuccessTable'; import SuccessTable from './overview/table/SuccessTable';
import Progressed from './overview/Progress';
import Accuracy from './overview/Accuracy'; import Accuracy from './overview/Accuracy';
import SearchSpace from './overview/SearchSpace'; import { ReBasicInfo } from './overview/experiment/BasicInfo';
import { BasicInfo } from './overview/BasicInfo'; import { ExpDuration } from './overview/count/ExpDuration';
import TrialInfo from './overview/TrialProfile'; import { ExpDurationContext } from './overview/count/ExpDurationContext';
import '../static/style/overview.scss'; import { TrialCount } from './overview/count/TrialCount';
import { Command } from './overview/experiment/Command';
import { TitleContext } from './overview/TitleContext';
import { itemStyle1, itemStyleSucceed, itemStyle2, entriesOption } from './overview/overviewConst';
import '../static/style/overview/overview.scss';
import '../static/style/logPath.scss'; import '../static/style/logPath.scss';
const stackTokens: IStackTokens = {
childrenGap: 30
};
const entriesOption = [
{ key: '10', text: 'Display top 10 trials' },
{ key: '20', text: 'Display top 20 trials' },
{ key: '30', text: 'Display top 30 trials' },
{ key: '50', text: 'Display top 50 trials' },
{ key: '100', text: 'Display top 100 trials' }
];
interface OverviewState { interface OverviewState {
trialConcurrency: number; trialConcurrency: number;
} }
export const TitleContext = React.createContext({ export const BestMetricContext = React.createContext({
text: '', bestAccuracy: 0
icon: '',
fontColor: ''
}); });
class Overview extends React.Component<{}, OverviewState> { class Overview extends React.Component<{}, OverviewState> {
static contextType = AppContext; static contextType = AppContext;
context!: React.ContextType<typeof AppContext>;
constructor(props) { constructor(props) {
super(props); super(props);
...@@ -58,11 +48,6 @@ class Overview extends React.Component<{}, OverviewState> { ...@@ -58,11 +48,6 @@ class Overview extends React.Component<{}, OverviewState> {
changeMetricGraphMode('min'); changeMetricGraphMode('min');
}; };
changeConcurrency = (val: number): void => {
this.setState({ trialConcurrency: val });
};
// updateEntries = (event: React.FormEvent<HTMLDivElement>, item: IDropdownOption | undefined): void => {
updateEntries = (event: React.FormEvent<HTMLDivElement>, item: any): void => { updateEntries = (event: React.FormEvent<HTMLDivElement>, item: any): void => {
if (item !== undefined) { if (item !== undefined) {
this.context.changeEntries(item.key); this.context.changeEntries(item.key);
...@@ -70,118 +55,125 @@ class Overview extends React.Component<{}, OverviewState> { ...@@ -70,118 +55,125 @@ class Overview extends React.Component<{}, OverviewState> {
}; };
render(): React.ReactNode { render(): React.ReactNode {
const { trialConcurrency } = this.state;
const bestTrials = this.findBestTrials(); const bestTrials = this.findBestTrials();
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const bestAccuracy = bestTrials.length > 0 ? bestTrials[0].accuracy! : NaN; const bestAccuracy = bestTrials.length > 0 ? bestTrials[0].accuracy! : NaN;
const accuracyGraphData = this.generateAccuracyGraph(bestTrials); const accuracyGraphData = this.generateAccuracyGraph(bestTrials);
const noDataMessage = bestTrials.length > 0 ? '' : 'No data'; const noDataMessage = bestTrials.length > 0 ? '' : 'No data';
const maxExecDuration = EXPERIMENT.profile.params.maxExecDuration;
const execDuration = EXPERIMENT.profile.execDuration;
return ( return (
<AppContext.Consumer> <AppContext.Consumer>
{(value): React.ReactNode => { {(value): React.ReactNode => {
const { experimentUpdateBroadcast, metricGraphMode, bestTrialEntries } = value; const { metricGraphMode, bestTrialEntries, updateOverviewPage } = value;
const titleMaxbgcolor = metricGraphMode === 'max' ? '#333' : '#b3b3b3'; const maxActive = metricGraphMode === 'max' ? 'active' : '';
const titleMinbgcolor = metricGraphMode === 'min' ? '#333' : '#b3b3b3'; const minActive = metricGraphMode === 'min' ? 'active' : '';
return ( return (
<div className='overview'> <div className='overview'>
{/* status and experiment block */} <div className='wrapper'>
<Stack className='bottomDiv bgNNI'> {/* exp params */}
<TitleContext.Provider value={{ text: 'Experiment', icon: '11.png', fontColor: '' }}> <div className='overviewBasicInfo'>
<Title1 /> <TitleContext.Provider value={{ text: 'Experiment', icon: 'AutoRacing' }}>
</TitleContext.Provider> <Title />
<BasicInfo />
</Stack>
<Stack horizontal className='overMessage bottomDiv'>
{/* status block */}
<Stack.Item grow className='prograph bgNNI borderRight'>
<TitleContext.Provider value={{ text: 'Status', icon: '5.png', fontColor: '' }}>
<Title1 />
</TitleContext.Provider>
<Progressed
bestAccuracy={bestAccuracy}
concurrency={trialConcurrency}
changeConcurrency={this.changeConcurrency}
experimentUpdateBroadcast={experimentUpdateBroadcast}
/>
</Stack.Item>
{/* experiment parameters search space tuner assessor... */}
<Stack.Item
grow
styles={{ root: { width: 450 } }}
className='overviewBoder borderRight bgNNI'
>
<TitleContext.Provider
value={{ text: 'Search space', icon: '10.png', fontColor: '' }}
>
<Title1 />
</TitleContext.Provider> </TitleContext.Provider>
<Stack className='experiment'> <BestMetricContext.Provider value={{ bestAccuracy: bestAccuracy }}>
<SearchSpace searchSpace={EXPERIMENT.searchSpace} /> <ReBasicInfo />
</Stack> </BestMetricContext.Provider>
</Stack.Item> </div>
<Stack.Item grow styles={{ root: { width: 450 } }} className='bgNNI'> {/* duration & trial numbers */}
<TitleContext.Provider value={{ text: 'Config', icon: '4.png', fontColor: '' }}> <div className='overviewProgress'>
<Title1 /> <div className='duration'>
</TitleContext.Provider> <TitleContext.Provider value={{ text: 'Duration', icon: 'Timer' }}>
<Stack className='experiment'> <Title />
{/* the scroll bar all the trial profile in the searchSpace div*/}
<div className='experiment searchSpace'>
<TrialInfo
experimentUpdateBroadcast={experimentUpdateBroadcast}
concurrency={trialConcurrency}
/>
</div>
</Stack>
</Stack.Item>
</Stack>
<Stack style={{ backgroundColor: '#fff' }}>
<Stack horizontal className='top10bg' style={{ position: 'relative', height: 42 }}>
<div className='title' onClick={this.clickMaxTop}>
<TitleContext.Provider
value={{
text: 'Top maximal trials',
icon: 'max.png',
fontColor: titleMaxbgcolor
}}
>
<Title1 />
</TitleContext.Provider> </TitleContext.Provider>
</div> <ExpDurationContext.Provider
<div className='title minTitle' onClick={this.clickMinTop}> value={{ maxExecDuration, execDuration, updateOverviewPage }}
<TitleContext.Provider
value={{
text: 'Top minimal trials',
icon: 'min.png',
fontColor: titleMinbgcolor
}}
> >
<Title1 /> <ExpDuration />
</TitleContext.Provider> </ExpDurationContext.Provider>
</div>
<div style={{ position: 'absolute', right: '2%', top: 8 }}>
<Dropdown
selectedKey={bestTrialEntries}
options={entriesOption}
onChange={this.updateEntries}
styles={{ root: { width: 170 } }}
/>
</div> </div>
</Stack> <div className='empty' />
<Stack horizontal tokens={stackTokens}> <div className='trialCount'>
<div style={{ width: '40%', position: 'relative' }}> <TitleContext.Provider value={{ text: 'Trial numbers', icon: 'NumberSymbol' }}>
<Accuracy <Title />
accuracyData={accuracyGraphData} </TitleContext.Provider>
accNodata={noDataMessage} <ExpDurationContext.Provider
height={404} value={{ maxExecDuration, execDuration, updateOverviewPage }}
/> >
</div> <TrialCount />
<div style={{ width: '60%' }}> </ExpDurationContext.Provider>
<SuccessTable trialIds={bestTrials.map(trial => trial.info.id)} />
</div> </div>
</Stack> </div>
</Stack> {/* table */}
<div className='overviewTable'>
<Stack horizontal>
<div style={itemStyleSucceed}>
<TitleContext.Provider value={{ text: 'Top trials', icon: 'BulletedList' }}>
<Title />
</TitleContext.Provider>
</div>
<div className='topTrialTitle'>
{/* <Stack horizontal horizontalAlign='space-between'> */}
<Stack horizontal horizontalAlign='end'>
<DefaultButton
onClick={this.clickMaxTop}
className={maxActive}
styles={{ root: { minWidth: 70, padding: 0 } }}
>
<Icon iconName='Market' />
<span className='max'>Max</span>
</DefaultButton>
<div className='mincenter'>
<DefaultButton
onClick={this.clickMinTop}
className={minActive}
styles={{ root: { minWidth: 70, padding: 0 } }}
>
<Icon iconName='MarketDown' />
<span className='max'>Min</span>
</DefaultButton>
</div>
<div>
<Stack horizontal>
<div className='chooseEntry'>Display top</div>
<div>
<Dropdown
selectedKey={bestTrialEntries}
options={entriesOption}
onChange={this.updateEntries}
styles={{ root: { width: 70 } }}
/>
</div>
</Stack>
</div>
</Stack>
</div>
</Stack>
<SuccessTable trialIds={bestTrials.map(trial => trial.info.id)} />
</div>
<div className='overviewCommand'>
<Command />
</div>
<div className='overviewChart'>
<Stack horizontal>
<div style={itemStyle1}>
<TitleContext.Provider
value={{ text: 'Trial metric chart', icon: 'HomeGroup' }}
>
<Title />
</TitleContext.Provider>
</div>
<div style={itemStyle2}>
<Stack className='maxmin' horizontal>
<div className='circle' />
<div>{`Top ${this.context.metricGraphMode}imal trials`}</div>
</Stack>
</div>
</Stack>
<Accuracy accuracyData={accuracyGraphData} accNodata={noDataMessage} height={380} />
</div>
</div>
</div> </div>
); );
}} }}
......
import * as React from 'react'; import * as React from 'react';
import { Stack, StackItem, Pivot, PivotItem, Dropdown, IDropdownOption, DefaultButton } from '@fluentui/react'; import { Stack, Pivot, PivotItem } from '@fluentui/react';
import { EXPERIMENT, TRIALS } from '../static/datamodel'; import { EXPERIMENT, TRIALS } from '../static/datamodel';
import { Trial } from '../static/model/trial';
import { AppContext } from '../App'; import { AppContext } from '../App';
import { tableListIcon } from './buttons/Icon';
import DefaultPoint from './trial-detail/DefaultMetricPoint'; import DefaultPoint from './trial-detail/DefaultMetricPoint';
import Duration from './trial-detail/Duration'; import Duration from './trial-detail/Duration';
import Para from './trial-detail/Para'; import Para from './trial-detail/Para';
...@@ -12,22 +10,13 @@ import TableList from './trial-detail/TableList'; ...@@ -12,22 +10,13 @@ import TableList from './trial-detail/TableList';
import '../static/style/trialsDetail.scss'; import '../static/style/trialsDetail.scss';
import '../static/style/search.scss'; import '../static/style/search.scss';
const searchOptions = [
{ key: 'id', text: 'Id' },
{ key: 'Trial No.', text: 'Trial No.' },
{ key: 'status', text: 'Status' },
{ key: 'parameters', text: 'Parameters' }
];
interface TrialDetailState { interface TrialDetailState {
tablePageSize: number; // table components val
whichChart: string; whichChart: string;
searchType: string;
searchFilter: (trial: Trial) => boolean;
} }
class TrialsDetail extends React.Component<{}, TrialDetailState> { class TrialsDetail extends React.Component<{}, TrialDetailState> {
static contextType = AppContext; static contextType = AppContext;
context!: React.ContextType<typeof AppContext>;
public interAccuracy = 0; public interAccuracy = 0;
public interAllTableList = 2; public interAllTableList = 2;
...@@ -37,71 +26,22 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> { ...@@ -37,71 +26,22 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
tablePageSize: 20, whichChart: 'Default metric'
whichChart: 'Default metric',
searchType: 'id',
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/explicit-function-return-type
searchFilter: trial => true
}; };
} }
// search a trial by trial No. | trial id | Parameters | Status
searchTrial = (event: React.ChangeEvent<HTMLInputElement>): void => {
const targetValue = event.target.value;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let filter = (trial: Trial): boolean => true;
if (!targetValue.trim()) {
this.setState({ searchFilter: filter });
return;
}
switch (this.state.searchType) {
case 'id':
filter = (trial): boolean => trial.info.id.toUpperCase().includes(targetValue.toUpperCase());
break;
case 'Trial No.':
filter = (trial): boolean => trial.info.sequenceId.toString() === targetValue;
break;
case 'status':
filter = (trial): boolean => trial.info.status.toUpperCase().includes(targetValue.toUpperCase());
break;
case 'parameters':
// TODO: support filters like `x: 2` (instead of `"x": 2`)
filter = (trial): boolean => JSON.stringify(trial.info.hyperParameters, null, 4).includes(targetValue);
break;
default:
alert(`Unexpected search filter ${this.state.searchType}`);
}
this.setState({ searchFilter: filter });
};
handleTablePageSizeSelect = (event: React.FormEvent<HTMLDivElement>, item: IDropdownOption | undefined): void => {
if (item !== undefined) {
this.setState({ tablePageSize: item.text === 'all' ? -1 : parseInt(item.text, 10) });
}
};
handleWhichTabs = (item: any): void => { handleWhichTabs = (item: any): void => {
this.setState({ whichChart: item.props.headerText }); this.setState({ whichChart: item.props.headerText });
}; };
updateSearchFilterType = (event: React.FormEvent<HTMLDivElement>, item: IDropdownOption | undefined): void => {
// clear input value and re-render table
if (item !== undefined) {
if (this.searchInput !== null) {
this.searchInput.value = '';
}
this.setState(() => ({ searchType: item.key.toString() }));
}
};
render(): React.ReactNode { render(): React.ReactNode {
const { tablePageSize, whichChart, searchType } = this.state; const { whichChart } = this.state;
const source = TRIALS.filter(this.state.searchFilter); const source = TRIALS.toArray();
const trialIds = TRIALS.filter(this.state.searchFilter).map(trial => trial.id); const trialIds = TRIALS.toArray().map(trial => trial.id);
return ( return (
<AppContext.Consumer> <AppContext.Consumer>
{(value): React.ReactNode => ( {(_value): React.ReactNode => (
<React.Fragment> <React.Fragment>
<div className='trial' id='tabsty'> <div className='trial' id='tabsty'>
<Pivot <Pivot
...@@ -143,59 +83,9 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> { ...@@ -143,59 +83,9 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> {
</div> </div>
{/* trial table list */} {/* trial table list */}
<div style={{ backgroundColor: '#fff' }}> <div style={{ backgroundColor: '#fff' }}>
<Stack horizontal className='panelTitle' style={{ marginTop: 10 }}>
<span style={{ marginRight: 12 }}>{tableListIcon}</span>
<span>Trial jobs</span>
</Stack>
<Stack horizontal className='allList'>
<StackItem grow={50}>
<DefaultButton
text='Compare'
className='allList-compare'
// use child-component tableList's function, the function is in child-component.
onClick={(): void => {
if (this.tableList) {
this.tableList.compareBtn();
}
}}
/>
</StackItem>
<StackItem grow={50}>
<Stack horizontal horizontalAlign='end' className='allList'>
<DefaultButton
className='allList-button-gap'
text='Add column'
onClick={(): void => {
if (this.tableList) {
this.tableList.addColumn();
}
}}
/>
<Dropdown
selectedKey={searchType}
options={searchOptions}
onChange={this.updateSearchFilterType}
styles={{ root: { width: 150 } }}
/>
<input
type='text'
className='allList-search-input'
placeholder={`Search by ${this.state.searchType}`}
onChange={this.searchTrial}
style={{ width: 230 }}
ref={(text): any => (this.searchInput = text)}
/>
</Stack>
</StackItem>
</Stack>
<TableList <TableList
pageSize={tablePageSize} tableSource={source}
tableSource={source.map(trial => trial.tableRecord)}
columnList={value.columnList}
changeColumn={value.changeColumn}
trialsUpdateBroadcast={this.context.trialsUpdateBroadcast} trialsUpdateBroadcast={this.context.trialsUpdateBroadcast}
// TODO: change any to specific type
ref={(tabList): any => (this.tableList = tabList)}
/> />
</div> </div>
</React.Fragment> </React.Fragment>
......
...@@ -11,11 +11,14 @@ const copy = <Icon iconName='Copy' />; ...@@ -11,11 +11,14 @@ const copy = <Icon iconName='Copy' />;
const tableListIcon = <Icon iconName='BulletedList' />; const tableListIcon = <Icon iconName='BulletedList' />;
const downLoadIcon = { iconName: 'Download' }; const downLoadIcon = { iconName: 'Download' };
const infoIconAbout = { iconName: 'info' }; const infoIconAbout = { iconName: 'info' };
const timeIcon = { iconName: 'Refresh' }; const timeIcon = { iconName: 'ReminderTime' };
const disableUpdates = { iconName: 'DisableUpdates' }; const disableUpdates = { iconName: 'DisableUpdates' };
const requency = { iconName: 'Timer' }; const requency = { iconName: 'Timer' };
const closeTimer = { iconName: 'Blocked2' }; const closeTimer = { iconName: 'Blocked2' };
const LineChart = <Icon iconName='LineChart' />; const LineChart = <Icon iconName='LineChart' />;
const Edit = <Icon iconName='Edit' />;
const CheckMark = <Icon iconName='CheckMark' />;
const Cancel = <Icon iconName='Cancel' />;
export { export {
infoIcon, infoIcon,
...@@ -31,5 +34,8 @@ export { ...@@ -31,5 +34,8 @@ export {
disableUpdates, disableUpdates,
requency, requency,
closeTimer, closeTimer,
LineChart LineChart,
Edit,
CheckMark,
Cancel
}; };
import * as React from 'react'; import * as React from 'react';
import { Dialog, DialogType, DialogFooter, Checkbox, PrimaryButton, DefaultButton } from '@fluentui/react'; import { Dialog, DialogType, DialogFooter, Checkbox, PrimaryButton, DefaultButton } from '@fluentui/react';
import { OPERATION } from '../../static/const';
interface ChangeColumnState { interface ChangeColumnState {
userSelectColumnList: string[]; // buffer, not saved yet
originSelectColumnList: string[]; currentSelected: string[];
} }
interface ChangeColumnProps { interface ChangeColumnProps {
isHideDialog: boolean; allColumns: SimpleColumn[]; // all column List
showColumn: string[]; // all column List selectedColumns: string[]; // user selected column list
selectedColumn: string[]; // user selected column list onSelectedChange: (val: string[]) => void;
changeColumn: (val: string[]) => void; onHideDialog: () => void;
hideShowColumnDialog: () => void; minSelected?: number;
}
interface SimpleColumn {
key: string; // key for management
name: string; // name to display
} }
interface CheckBoxItems { interface CheckBoxItems {
...@@ -20,12 +24,12 @@ interface CheckBoxItems { ...@@ -20,12 +24,12 @@ interface CheckBoxItems {
checked: boolean; checked: boolean;
onChange: () => void; onChange: () => void;
} }
class ChangeColumnComponent extends React.Component<ChangeColumnProps, ChangeColumnState> { class ChangeColumnComponent extends React.Component<ChangeColumnProps, ChangeColumnState> {
constructor(props: ChangeColumnProps) { constructor(props: ChangeColumnProps) {
super(props); super(props);
this.state = { this.state = {
userSelectColumnList: this.props.selectedColumn, currentSelected: this.props.selectedColumns
originSelectColumnList: this.props.selectedColumn
}; };
} }
...@@ -38,97 +42,50 @@ class ChangeColumnComponent extends React.Component<ChangeColumnProps, ChangeCol ...@@ -38,97 +42,50 @@ class ChangeColumnComponent extends React.Component<ChangeColumnProps, ChangeCol
label: string, label: string,
val?: boolean val?: boolean
): void => { ): void => {
const source: string[] = JSON.parse(JSON.stringify(this.state.userSelectColumnList)); const source: string[] = [...this.state.currentSelected];
if (val === true) { if (val === true) {
if (!source.includes(label)) { if (!source.includes(label)) {
source.push(label); source.push(label);
this.setState(() => ({ userSelectColumnList: source })); this.setState({ currentSelected: source });
} }
} else { } else {
if (source.includes(label)) { // remove from source
// remove from source const result = source.filter(item => item !== label);
const result = source.filter(item => item !== label); this.setState({ currentSelected: result });
this.setState(() => ({ userSelectColumnList: result }));
}
} }
}; };
saveUserSelectColumn = (): void => { saveUserSelectColumn = (): void => {
const { userSelectColumnList } = this.state; const { currentSelected } = this.state;
const { showColumn } = this.props; const { allColumns, onSelectedChange } = this.props;
// sort by Trial No. | ID | Duration | Start Time | End Time | ... const selectedColumns = allColumns.map(column => column.key).filter(key => currentSelected.includes(key));
const sortColumn: string[] = []; onSelectedChange(selectedColumns);
/** this.hideDialog();
*
* TODO: use this function to refactor sort column
* search space might orderless
showColumn.map(item => {
userSelectColumnList.map(key => {
if (item === key || key.includes('search space')) {
if (!sortColumn.includes(key)) {
sortColumn.push(key);
}
}
});
});
*/
// push ![Operation] ![search space] column
showColumn.map(item => {
userSelectColumnList.map(key => {
if (item === key && item !== OPERATION) {
sortColumn.push(key);
}
});
});
// push search space key
userSelectColumnList.map(index => {
if (index.includes('search space')) {
if (!sortColumn.includes(index)) {
sortColumn.push(index);
}
}
});
// push Operation
if (userSelectColumnList.includes(OPERATION)) {
sortColumn.push(OPERATION);
}
this.props.changeColumn(sortColumn);
this.hideDialog(); // hide dialog
};
hideDialog = (): void => {
this.props.hideShowColumnDialog();
}; };
// user exit dialog // user exit dialog
cancelOption = (): void => { cancelOption = (): void => {
// reset select column // reset select column
const { originSelectColumnList } = this.state; this.setState({ currentSelected: this.props.selectedColumns }, () => {
this.setState({ userSelectColumnList: originSelectColumnList }, () => {
this.hideDialog(); this.hideDialog();
}); });
}; };
private hideDialog = (): void => {
this.props.onHideDialog();
};
render(): React.ReactNode { render(): React.ReactNode {
const { showColumn, isHideDialog } = this.props; const { allColumns, minSelected } = this.props;
const { userSelectColumnList } = this.state; const { currentSelected } = this.state;
const renderOptions: Array<CheckBoxItems> = [];
showColumn.map(item => {
if (userSelectColumnList.includes(item)) {
// selected column name
renderOptions.push({ label: item, checked: true, onChange: this.makeChangeHandler(item) });
} else {
renderOptions.push({ label: item, checked: false, onChange: this.makeChangeHandler(item) });
}
});
return ( return (
<div> <div>
<Dialog <Dialog
hidden={isHideDialog} // required field! hidden={false}
dialogContentProps={{ dialogContentProps={{
type: DialogType.largeHeader, type: DialogType.largeHeader,
title: 'Change table column', title: 'Customize columns',
subText: 'You can chose which columns you want to see in the table.' subText: 'You can choose which columns you wish to see.'
}} }}
modalProps={{ modalProps={{
isBlocking: false, isBlocking: false,
...@@ -136,12 +93,22 @@ class ChangeColumnComponent extends React.Component<ChangeColumnProps, ChangeCol ...@@ -136,12 +93,22 @@ class ChangeColumnComponent extends React.Component<ChangeColumnProps, ChangeCol
}} }}
> >
<div className='columns-height'> <div className='columns-height'>
{renderOptions.map(item => { {allColumns.map(item => (
return <Checkbox key={item.label} {...item} styles={{ root: { marginBottom: 8 } }} />; <Checkbox
})} key={item.key}
label={item.name}
checked={currentSelected.includes(item.key)}
onChange={this.makeChangeHandler(item.key)}
styles={{ root: { marginBottom: 8 } }}
/>
))}
</div> </div>
<DialogFooter> <DialogFooter>
<PrimaryButton text='Save' onClick={this.saveUserSelectColumn} /> <PrimaryButton
text='Save'
onClick={this.saveUserSelectColumn}
disabled={currentSelected.length < (minSelected === undefined ? 1 : minSelected)}
/>
<DefaultButton text='Cancel' onClick={this.cancelOption} /> <DefaultButton text='Cancel' onClick={this.cancelOption} />
</DialogFooter> </DialogFooter>
</Dialog> </Dialog>
......
import * as React from 'react'; import * as React from 'react';
import { renderToString } from 'react-dom/server';
import { Stack, Modal, IconButton, IDragOptions, ContextualMenu } from '@fluentui/react'; import { Stack, Modal, IconButton, IDragOptions, ContextualMenu } from '@fluentui/react';
import ReactEcharts from 'echarts-for-react'; import ReactEcharts from 'echarts-for-react';
import IntermediateVal from '../public-child/IntermediateVal'; import { TooltipForIntermediate, TableObj, SingleAxis } from '../../static/interface';
import { TRIALS } from '../../static/datamodel';
import { TableRecord, Intermedia, TooltipForIntermediate } from '../../static/interface';
import { contentStyles, iconButtonStyles } from '../buttons/ModalTheme'; import { contentStyles, iconButtonStyles } from '../buttons/ModalTheme';
import '../../static/style/compare.scss'; import '../../static/style/compare.scss';
import { convertDuration, parseMetrics } from '../../static/function';
import { EXPERIMENT, TRIALS } from '../../static/datamodel';
function _getWebUIWidth(): number {
return window.innerWidth;
}
const dragOptions: IDragOptions = { const dragOptions: IDragOptions = {
moveMenuItemText: 'Move', moveMenuItemText: 'Move',
...@@ -13,79 +18,81 @@ const dragOptions: IDragOptions = { ...@@ -13,79 +18,81 @@ const dragOptions: IDragOptions = {
menu: ContextualMenu menu: ContextualMenu
}; };
// the modal of trial compare // TODO: this should be refactored to the common modules
// copied from trial.ts
function _parseIntermediates(trial: TableObj): number[] {
const intermediates: number[] = [];
for (const metric of trial.intermediates) {
if (metric === undefined) {
break;
}
const parsedMetric = parseMetrics(metric.data);
if (typeof parsedMetric === 'object') {
// TODO: should handle more types of metric keys
intermediates.push(parsedMetric.default);
} else {
intermediates.push(parsedMetric);
}
}
return intermediates;
}
interface Item {
id: string;
sequenceId: number;
duration: string;
parameters: Map<string, any>;
metrics: Map<string, any>;
intermediates: number[];
}
interface CompareProps { interface CompareProps {
compareStacks: Array<TableRecord>; trials: TableObj[];
cancelFunc: () => void; title: string;
showDetails: boolean;
onHideDialog: () => void;
} }
class Compare extends React.Component<CompareProps, {}> { class Compare extends React.Component<CompareProps, {}> {
public _isCompareMount!: boolean;
constructor(props: CompareProps) { constructor(props: CompareProps) {
super(props); super(props);
} }
intermediate = (): React.ReactNode => { private _generateTooltipSummary(row: Item, metricKey: string): string {
const { compareStacks } = this.props; return renderToString(
const trialIntermediate: Array<Intermedia> = []; <div className='tooldetailAccuracy'>
const idsList: string[] = []; <div>Trial ID: {row.id}</div>
compareStacks.forEach(element => { <div>Default metric: {row.metrics.get(metricKey) || 'N/A'}</div>
const trial = TRIALS.getTrial(element.id); </div>
trialIntermediate.push({ );
name: element.id, }
data: trial.description.intermediate,
type: 'line', private _intermediates(items: Item[], metricKey: string): React.ReactNode {
hyperPara: trial.description.parameters // Precondition: make sure `items` is not empty
}); const xAxisMax = Math.max(...items.map(item => item.intermediates.length));
idsList.push(element.id); const xAxis = Array(xAxisMax)
}); .fill(0)
// find max intermediate number .map((_, i) => i + 1); // [1, 2, 3, ..., xAxisMax]
trialIntermediate.sort((a, b) => { const dataForEchart = items.map(item => ({
return b.data.length - a.data.length; name: item.id,
}); data: item.intermediates,
const legend: string[] = []; type: 'line'
// max length }));
const length = trialIntermediate[0] !== undefined ? trialIntermediate[0].data.length : 0; const legend = dataForEchart.map(item => item.name);
const xAxis: number[] = [];
trialIntermediate.forEach(element => {
legend.push(element.name);
});
for (let i = 1; i <= length; i++) {
xAxis.push(i);
}
const option = { const option = {
tooltip: { tooltip: {
trigger: 'item', trigger: 'item',
enterable: true, enterable: true,
position: function(point: number[], data: TooltipForIntermediate): number[] { position: (point: number[], data: TooltipForIntermediate): [number, number] => {
if (data.dataIndex < length / 2) { if (data.dataIndex < length / 2) {
return [point[0], 80]; return [point[0], 80];
} else { } else {
return [point[0] - 300, 80]; return [point[0] - 300, 80];
} }
}, },
formatter: function(data: TooltipForIntermediate): React.ReactNode { formatter: (data: TooltipForIntermediate): string => {
const trialId = data.seriesName; const item = items.find(k => k.id === data.seriesName) as Item;
let obj = {}; return this._generateTooltipSummary(item, metricKey);
const temp = trialIntermediate.find(key => key.name === trialId);
if (temp !== undefined) {
obj = temp.hyperPara;
}
return (
'<div class="tooldetailAccuracy">' +
'<div>Trial ID: ' +
trialId +
'</div>' +
'<div>Intermediate: ' +
data.data +
'</div>' +
'<div>Parameters: ' +
'<pre>' +
JSON.stringify(obj, null, 4) +
'</pre>' +
'</div>' +
'</div>'
);
} }
}, },
grid: { grid: {
...@@ -96,12 +103,11 @@ class Compare extends React.Component<CompareProps, {}> { ...@@ -96,12 +103,11 @@ class Compare extends React.Component<CompareProps, {}> {
legend: { legend: {
type: 'scroll', type: 'scroll',
right: 40, right: 40,
left: idsList.length > 6 ? 80 : null, left: legend.length > 6 ? 80 : null,
data: idsList data: legend
}, },
xAxis: { xAxis: {
type: 'category', type: 'category',
// name: '# Intermediate',
boundaryGap: false, boundaryGap: false,
data: xAxis data: xAxis
}, },
...@@ -110,7 +116,7 @@ class Compare extends React.Component<CompareProps, {}> { ...@@ -110,7 +116,7 @@ class Compare extends React.Component<CompareProps, {}> {
name: 'Metric', name: 'Metric',
scale: true scale: true
}, },
series: trialIntermediate series: dataForEchart
}; };
return ( return (
<ReactEcharts <ReactEcharts
...@@ -119,108 +125,92 @@ class Compare extends React.Component<CompareProps, {}> { ...@@ -119,108 +125,92 @@ class Compare extends React.Component<CompareProps, {}> {
notMerge={true} // update now notMerge={true} // update now
/> />
); );
}; }
// render table column ---
initColumn = (): React.ReactNode => {
const idList: string[] = [];
const sequenceIdList: number[] = [];
const durationList: number[] = [];
const compareStacks = this.props.compareStacks.map(tableRecord => TRIALS.getTrial(tableRecord.id)); private _renderRow(
key: string,
rowName: string,
className: string,
items: Item[],
formatter: (item: Item) => string
): React.ReactNode {
return (
<tr key={key}>
<td className='column'>{rowName}</td>
{items.map(item => (
<td className={className} key={item.id}>
{formatter(item)}
</td>
))}
</tr>
);
}
const parameterList: Array<object> = []; private _overlapKeys(s: Map<string, any>[]): string[] {
let parameterKeys: string[] = []; // Calculate the overlapped keys for multiple
if (compareStacks.length !== 0) { const intersection: string[] = [];
parameterKeys = Object.keys(compareStacks[0].description.parameters); for (const i of s[0].keys()) {
} let inAll = true;
compareStacks.forEach(temp => { for (const t of s) {
idList.push(temp.id); if (!Array.from(t.keys()).includes(i)) {
sequenceIdList.push(temp.sequenceId); inAll = false;
durationList.push(temp.duration); break;
parameterList.push(temp.description.parameters); }
}); }
let isComplexSearchSpace; if (inAll) {
if (parameterList.length > 0) { intersection.push(i);
isComplexSearchSpace = typeof parameterList[0][parameterKeys[0]] === 'object' ? true : false; }
} }
const width = this.getWebUIWidth(); return intersection;
let scrollClass; }
// render table column ---
private _columns(items: Item[]): React.ReactNode {
// Precondition: make sure `items` is not empty
const width = _getWebUIWidth();
let scrollClass: string = '';
if (width > 1200) { if (width > 1200) {
scrollClass = idList.length > 3 ? 'flex' : ''; scrollClass = items.length > 3 ? 'flex' : '';
} else if (width < 700) { } else if (width < 700) {
scrollClass = idList.length > 1 ? 'flex' : ''; scrollClass = items.length > 1 ? 'flex' : '';
} else { } else {
scrollClass = idList.length > 2 ? 'flex' : ''; scrollClass = items.length > 2 ? 'flex' : '';
} }
const parameterKeys = this._overlapKeys(items.map(item => item.parameters));
const metricKeys = this._overlapKeys(items.map(item => item.metrics));
return ( return (
<table className={`compare-modal-table ${scrollClass}`}> <table className={`compare-modal-table ${scrollClass}`}>
<tbody> <tbody>
<tr> {this._renderRow('id', 'ID', 'value idList', items, item => item.id)}
<td className='column'>Id</td> {this._renderRow('trialnum', 'Trial No.', 'value', items, item => item.sequenceId.toString())}
{Object.keys(idList).map(key => ( {this._renderRow('duration', 'Duration', 'value', items, item => item.duration)}
<td className='value idList' key={key}> {parameterKeys.map(k =>
{idList[key]} this._renderRow(`space_${k}`, k, 'value', items, item => item.parameters.get(k))
</td> )}
))} {metricKeys.map(k =>
</tr> this._renderRow(`metrics_${k}`, `Metric: ${k}`, 'value', items, item => item.metrics.get(k))
<tr> )}
<td className='column'>Trial No.</td>
{Object.keys(sequenceIdList).map(key => (
<td className='value idList' key={key}>
{sequenceIdList[key]}
</td>
))}
</tr>
<tr>
<td className='column'>Default metric</td>
{Object.keys(compareStacks).map(index => (
<td className='value' key={index}>
<IntermediateVal trialId={compareStacks[index].id} />
</td>
))}
</tr>
<tr>
<td className='column'>duration</td>
{Object.keys(durationList).map(index => (
<td className='value' key={index}>
{durationList[index]}
</td>
))}
</tr>
{isComplexSearchSpace
? null
: Object.keys(parameterKeys).map(index => (
<tr key={index}>
<td className='column' key={index}>
{parameterKeys[index]}
</td>
{Object.keys(parameterList).map(key => (
<td key={key} className='value'>
{parameterList[key][parameterKeys[index]]}
</td>
))}
</tr>
))}
</tbody> </tbody>
</table> </table>
); );
};
getWebUIWidth = (): number => {
return window.innerWidth;
};
componentDidMount(): void {
this._isCompareMount = true;
}
componentWillUnmount(): void {
this._isCompareMount = false;
} }
render(): React.ReactNode { render(): React.ReactNode {
const { cancelFunc } = this.props; const { onHideDialog, trials, title, showDetails } = this.props;
const flatten = (m: Map<SingleAxis, any>): Map<string, any> => {
return new Map(Array.from(m).map(([key, value]) => [key.baseName, value]));
};
const inferredSearchSpace = TRIALS.inferredSearchSpace(EXPERIMENT.searchSpaceNew);
const items: Item[] = trials.map(trial => ({
id: trial.id,
sequenceId: trial.sequenceId,
duration: convertDuration(trial.duration),
parameters: flatten(trial.parameters(inferredSearchSpace)),
metrics: flatten(trial.metrics(TRIALS.inferredMetricSpace())),
intermediates: _parseIntermediates(trial)
}));
const metricKeys = this._overlapKeys(items.map(item => item.metrics));
const defaultMetricKey = !metricKeys || metricKeys.includes('default') ? 'default' : metricKeys[0];
return ( return (
<Modal <Modal
...@@ -229,22 +219,23 @@ class Compare extends React.Component<CompareProps, {}> { ...@@ -229,22 +219,23 @@ class Compare extends React.Component<CompareProps, {}> {
className='compare-modal' className='compare-modal'
allowTouchBodyScroll={true} allowTouchBodyScroll={true}
dragOptions={dragOptions} dragOptions={dragOptions}
onDismiss={onHideDialog}
> >
<div> <div>
<div className={contentStyles.header}> <div className={contentStyles.header}>
<span>Compare trials</span> <span>{title}</span>
<IconButton <IconButton
styles={iconButtonStyles} styles={iconButtonStyles}
iconProps={{ iconName: 'Cancel' }} iconProps={{ iconName: 'Cancel' }}
ariaLabel='Close popup modal' ariaLabel='Close popup modal'
onClick={cancelFunc} onClick={onHideDialog}
/> />
</div> </div>
<Stack className='compare-modal-intermediate'> <Stack className='compare-modal-intermediate'>
{this.intermediate()} {this._intermediates(items, defaultMetricKey)}
<Stack className='compare-yAxis'># Intermediate result</Stack> <Stack className='compare-yAxis'># Intermediate result</Stack>
</Stack> </Stack>
<Stack>{this.initColumn()}</Stack> {showDetails && <Stack>{this._columns(items)}</Stack>}
</div> </div>
</Modal> </Modal>
); );
......
...@@ -22,7 +22,7 @@ class Accuracy extends React.Component<AccuracyProps, {}> { ...@@ -22,7 +22,7 @@ class Accuracy extends React.Component<AccuracyProps, {}> {
render(): React.ReactNode { render(): React.ReactNode {
const { accNodata, accuracyData, height } = this.props; const { accNodata, accuracyData, height } = this.props;
return ( return (
<div> <div style={{ position: 'relative' }}>
<ReactEcharts <ReactEcharts
option={accuracyData} option={accuracyData}
style={{ style={{
......
import React from 'react';
import { Stack, TooltipHost } from '@fluentui/react';
import { EXPERIMENT } from '../../static/datamodel';
import { formatTimestamp } from '../../static/function';
export const BasicInfo = (): any => (
<Stack horizontal horizontalAlign='space-between' className='main'>
<Stack.Item grow={3} className='padItem basic'>
<p>Name</p>
<div>{EXPERIMENT.profile.params.experimentName}</div>
</Stack.Item>
<Stack.Item grow={3} className='padItem basic'>
<p>ID</p>
<div>{EXPERIMENT.profile.id}</div>
</Stack.Item>
<Stack.Item grow={3} className='padItem basic'>
<p>Start time</p>
<div className='nowrap'>{formatTimestamp(EXPERIMENT.profile.startTime)}</div>
</Stack.Item>
<Stack.Item grow={3} className='padItem basic'>
<p>End time</p>
<div className='nowrap'>{formatTimestamp(EXPERIMENT.profile.endTime)}</div>
</Stack.Item>
<Stack.Item className='padItem basic'>
<p>Log directory</p>
<div className='nowrap'>
<TooltipHost
// Tooltip message content
content={EXPERIMENT.profile.logDir || 'unknown'}
calloutProps={{ gapSpace: 0 }}
styles={{ root: { display: 'inline-block' } }}
>
{/* show logDir */}
{EXPERIMENT.profile.logDir || 'unknown'}
</TooltipHost>
</div>
</Stack.Item>
<Stack.Item className='padItem basic'>
<p>Training platform</p>
<div className='nowrap'>{EXPERIMENT.profile.params.trainingServicePlatform}</div>
</Stack.Item>
</Stack>
);
import * as React from 'react';
import { Stack, PrimaryButton } from '@fluentui/react';
interface ConcurrencyInputProps {
value: number;
updateValue: (val: string) => void;
}
interface ConcurrencyInputStates {
editting: boolean;
}
class ConcurrencyInput extends React.Component<ConcurrencyInputProps, ConcurrencyInputStates> {
private input = React.createRef<HTMLInputElement>();
constructor(props: ConcurrencyInputProps) {
super(props);
this.state = { editting: false };
}
save = (): void => {
if (this.input.current !== null) {
this.props.updateValue(this.input.current.value);
this.setState({ editting: false });
}
};
cancel = (): void => {
this.setState({ editting: false });
};
edit = (): void => {
this.setState({ editting: true });
};
render(): React.ReactNode {
if (this.state.editting) {
return (
<Stack horizontal className='inputBox'>
<input
type='number'
className='concurrencyInput'
defaultValue={this.props.value.toString()}
ref={this.input}
/>
<PrimaryButton text='Save' onClick={this.save} />
<PrimaryButton
text='Cancel'
style={{ display: 'inline-block', marginLeft: 1 }}
onClick={this.cancel}
/>
</Stack>
);
} else {
return (
<Stack horizontal className='inputBox'>
<input type='number' className='concurrencyInput' disabled={true} value={this.props.value} />
<PrimaryButton text='Edit' onClick={this.edit} />
</Stack>
);
}
}
}
export default ConcurrencyInput;
import * as React from 'react';
import {
Stack,
Callout,
Link,
IconButton,
FontWeights,
mergeStyleSets,
getId,
getTheme,
StackItem,
TooltipHost
} from '@fluentui/react';
import axios from 'axios';
import { MANAGER_IP, CONCURRENCYTOOLTIP } from '../../static/const';
import { EXPERIMENT, TRIALS } from '../../static/datamodel';
import { convertTime } from '../../static/function';
import ConcurrencyInput from './NumInput';
import ProgressBar from './ProgressItem';
import LogDrawer from '../modals/LogPanel';
import MessageInfo from '../modals/MessageInfo';
import { infoIcon } from '../buttons/Icon';
import '../../static/style/progress.scss';
import '../../static/style/probar.scss';
interface ProgressProps {
concurrency: number;
bestAccuracy: number;
changeConcurrency: (val: number) => void;
experimentUpdateBroadcast: number;
}
interface ProgressState {
isShowLogDrawer: boolean;
isCalloutVisible?: boolean;
isShowSucceedInfo: boolean;
info: string;
typeInfo: string;
}
const itemStyles: React.CSSProperties = {
height: 50,
width: 100
};
const theme = getTheme();
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
}
]
});
class Progressed extends React.Component<ProgressProps, ProgressState> {
private menuButtonElement!: HTMLDivElement | null;
private labelId: string = getId('callout-label');
private descriptionId: string = getId('callout-description');
constructor(props: ProgressProps) {
super(props);
this.state = {
isShowLogDrawer: false,
isCalloutVisible: false,
isShowSucceedInfo: false,
info: '',
typeInfo: 'success'
};
}
hideSucceedInfo = (): void => {
this.setState(() => ({ isShowSucceedInfo: false }));
};
/**
* info: message content
* typeInfo: message type: success | error...
* continuousTime: show time, 2000ms
*/
showMessageInfo = (info: string, typeInfo: string): void => {
this.setState(() => ({
info,
typeInfo,
isShowSucceedInfo: true
}));
setTimeout(this.hideSucceedInfo, 2000);
};
editTrialConcurrency = async (userInput: string): Promise<void> => {
if (!userInput.match(/^[1-9]\d*$/)) {
this.showMessageInfo('Please enter a positive integer!', 'error');
return;
}
const newConcurrency = parseInt(userInput, 10);
if (newConcurrency === this.props.concurrency) {
this.showMessageInfo('Trial concurrency has not changed', 'error');
return;
}
const newProfile = Object.assign({}, EXPERIMENT.profile);
newProfile.params.trialConcurrency = newConcurrency;
// rest api, modify trial concurrency value
try {
const res = await axios.put(`${MANAGER_IP}/experiment`, newProfile, {
// eslint-disable-next-line @typescript-eslint/camelcase
params: { update_type: 'TRIAL_CONCURRENCY' }
});
if (res.status === 200) {
this.showMessageInfo('Successfully updated trial concurrency', 'success');
// NOTE: should we do this earlier in favor of poor networks?
this.props.changeConcurrency(newConcurrency);
}
} catch (error) {
if (error.response && error.response.data.error) {
this.showMessageInfo(`Failed to update trial concurrency\n${error.response.data.error}`, 'error');
} else if (error.response) {
this.showMessageInfo(
`Failed to update trial concurrency\nServer responsed ${error.response.status}`,
'error'
);
} else if (error.message) {
this.showMessageInfo(`Failed to update trial concurrency\n${error.message}`, 'error');
} else {
this.showMessageInfo(`Failed to update trial concurrency\nUnknown error`, 'error');
}
}
};
isShowDrawer = (): void => {
this.setState({ isShowLogDrawer: true });
};
closeDrawer = (): void => {
this.setState({ isShowLogDrawer: false });
};
onDismiss = (): void => {
this.setState({ isCalloutVisible: false });
};
onShow = (): void => {
this.setState({ isCalloutVisible: true });
};
render(): React.ReactNode {
const { bestAccuracy } = this.props;
const { isShowLogDrawer, isCalloutVisible, isShowSucceedInfo, info, typeInfo } = this.state;
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;
const percent = EXPERIMENT.profile.execDuration / EXPERIMENT.profile.params.maxExecDuration;
const remaining = convertTime(EXPERIMENT.profile.params.maxExecDuration - EXPERIMENT.profile.execDuration);
const maxDuration = convertTime(EXPERIMENT.profile.params.maxExecDuration);
const maxTrialNum = EXPERIMENT.profile.params.maxTrialNum;
const execDuration = convertTime(EXPERIMENT.profile.execDuration);
return (
<Stack className='progress' id='barBack'>
<Stack className='basic lineBasic'>
<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={(val): any => (this.menuButtonElement = val)}>
<IconButton
iconProps={{ iconName: 'info' }}
onClick={isCalloutVisible ? this.onDismiss : this.onShow}
/>
</div>
{isCalloutVisible && (
<Callout
className={styles.callout}
ariaLabelledBy={this.labelId}
ariaDescribedBy={this.descriptionId}
role='alertdialog'
gapSpace={0}
target={this.menuButtonElement}
onDismiss={this.onDismiss}
setInitialFocus={true}
>
<div className={styles.header}>
<p className={styles.title} id={this.labelId}>
Error
</p>
</div>
<div className={styles.inner}>
<p className={styles.subtext} id={this.descriptionId}>
{EXPERIMENT.error}
</p>
<div className={styles.actions}>
<Link className={styles.link} onClick={this.isShowDrawer}>
Learn about
</Link>
</div>
</div>
</Callout>
)}
</div>
) : null}
</Stack>
</Stack>
<ProgressBar
who='Duration'
percent={percent}
description={execDuration}
bgclass={EXPERIMENT.status}
maxString={`Max duration: ${maxDuration}`}
/>
<ProgressBar
who='Trial numbers'
percent={bar2Percent}
description={bar2.toString()}
bgclass={EXPERIMENT.status}
maxString={`Max trial number: ${maxTrialNum}`}
/>
<Stack className='basic colorOfbasic mess' horizontal>
<StackItem grow={50}>
<p>Best metric</p>
<div>{isNaN(bestAccuracy) ? 'N/A' : bestAccuracy.toFixed(6)}</div>
</StackItem>
<StackItem>
{isShowSucceedInfo && <MessageInfo className='info' typeInfo={typeInfo} info={info} />}
</StackItem>
</Stack>
<Stack horizontal horizontalAlign='space-between' className='mess'>
<span style={itemStyles} className='basic colorOfbasic'>
<p>Spent</p>
<div>{execDuration}</div>
</span>
<span style={itemStyles} className='basic colorOfbasic'>
<p>Remaining</p>
<div className='time'>{remaining}</div>
</span>
<span style={itemStyles}>
{/* modify concurrency */}
<TooltipHost content={CONCURRENCYTOOLTIP}>
<p className='cursor'>
Concurrency<span className='progress-info'>{infoIcon}</span>
</p>
</TooltipHost>
<ConcurrencyInput value={this.props.concurrency} updateValue={this.editTrialConcurrency} />
</span>
<span style={itemStyles} className='basic colorOfbasic'></span>
</Stack>
<Stack horizontal horizontalAlign='space-between' className='mess'>
<div style={itemStyles} className='basic colorOfbasic'>
<p>Running</p>
<div>{count.get('RUNNING')}</div>
</div>
<div style={itemStyles} className='basic colorOfbasic'>
<p>Succeeded</p>
<div>{count.get('SUCCEEDED')}</div>
</div>
<div style={itemStyles} className='basic'>
<p>Stopped</p>
<div>{stoppedCount}</div>
</div>
<div style={itemStyles} className='basic'>
<p>Failed</p>
<div>{count.get('FAILED')}</div>
</div>
</Stack>
{/* learn about click -> default active key is dispatcher. */}
{isShowLogDrawer ? <LogDrawer closeDrawer={this.closeDrawer} activeTab='dispatcher' /> : null}
</Stack>
);
}
}
export default Progressed;
import * as React from 'react';
import { Stack, StackItem, ProgressIndicator } from '@fluentui/react';
interface ProItemProps {
who: string;
percent: number;
description: string;
maxString: string;
bgclass: string;
}
class ProgressBar extends React.Component<ProItemProps, {}> {
constructor(props: ProItemProps) {
super(props);
}
render(): React.ReactNode {
const { who, percent, description, maxString, bgclass } = this.props;
return (
<div>
<Stack horizontal className={`probar ${bgclass}`}>
<div className='name'>{who}</div>
<div className='showProgress' style={{ width: '78%' }}>
<ProgressIndicator barHeight={30} percentComplete={percent} />
<Stack horizontal className='boundary'>
<StackItem grow={30}>0</StackItem>
<StackItem className='right' grow={70}>
{maxString}
</StackItem>
</Stack>
</div>
<div className='description' style={{ width: '22%' }}>
{description}
</div>
</Stack>
<br />
</div>
);
}
}
export default ProgressBar;
import * as React from 'react';
import MonacoEditor from 'react-monaco-editor';
import { MONACO } from '../../static/const';
interface SearchspaceProps {
searchSpace: object;
}
class SearchSpace extends React.Component<SearchspaceProps, {}> {
constructor(props: SearchspaceProps) {
super(props);
}
render(): React.ReactNode {
const { searchSpace } = this.props;
return (
<div className='searchSpace'>
<MonacoEditor
height='361'
language='json'
theme='vs-light'
value={JSON.stringify(searchSpace, null, 2)}
options={MONACO}
/>
</div>
);
}
}
export default SearchSpace;
import * as React from 'react'; import React from 'react';
import { Stack } from '@fluentui/react'; import { Stack, Icon, initializeIcons } from '@fluentui/react';
import { TitleContext } from '../Overview'; import { TitleContext } from './TitleContext';
import '../../static/style/overviewTitle.scss'; import '../../static/style/overview/overviewTitle.scss';
initializeIcons();
export const Title1 = (): any => ( export const Title = (): any => (
<TitleContext.Consumer> <TitleContext.Consumer>
{(value): React.ReactNode => ( {(value): React.ReactNode => (
<Stack horizontal className='panelTitle'> <Stack horizontal className='panelTitle'>
<img src={require(`../../static/img/icon/${value.icon}`)} alt='icon' /> <Icon iconName={value.icon} />
<span style={{ color: value.fontColor }}>{value.text}</span> <span>{value.text}</span>
</Stack> </Stack>
)} )}
</TitleContext.Consumer> </TitleContext.Consumer>
......
import * as React from 'react';
export const TitleContext = React.createContext({
text: '',
icon: ''
});
import * as React from 'react';
import MonacoEditor from 'react-monaco-editor';
import { MONACO } from '../../static/const';
import { EXPERIMENT } from '../../static/datamodel';
interface TrialInfoProps {
experimentUpdateBroadcast: number;
concurrency: number;
}
class TrialInfo extends React.Component<TrialInfoProps, {}> {
constructor(props: TrialInfoProps) {
super(props);
}
render(): React.ReactNode {
const blacklist = [
'id',
'logDir',
'startTime',
'endTime',
'experimentName',
'searchSpace',
'trainingServicePlatform'
];
const filter = (key: string, val: any): any => {
if (key === 'trialConcurrency') {
return this.props.concurrency;
}
return blacklist.includes(key) ? undefined : val;
};
const profile = JSON.stringify(EXPERIMENT.profile, filter, 2);
return (
<div className='profile'>
<MonacoEditor
width='100%'
height='361'
language='json'
theme='vs-light'
value={profile}
options={MONACO}
/>
</div>
);
}
}
export default TrialInfo;
import React, { useState, useCallback, useContext } from 'react';
import axios from 'axios';
import { EXPERIMENT } from '../../../static/datamodel';
import { EditExpeParamContext } from './context';
import { MANAGER_IP } from '../../../static/const';
import { convertTimeToSecond } from '../../../static/function';
import { Edit, CheckMark, Cancel } from '../../buttons/Icon';
import MessageInfo from '../../modals/MessageInfo';
import '../../../static/style/overview/count.scss';
const DurationInputRef = React.createRef<HTMLInputElement>();
export const EditExperimentParam = (): any => {
const [isShowPencil, setShowPencil] = useState(true);
const [isShowSucceedInfo, setShowSucceedInfo] = useState(false);
const [typeInfo, setTypeInfo] = useState('');
const [info, setInfo] = useState('');
const showPencil = useCallback(() => {
setShowPencil(true);
}, []);
const hidePencil = useCallback(() => {
setShowPencil(false);
}, []);
const showSucceedInfo = useCallback(() => setShowSucceedInfo(true), []);
const hideSucceedInfo = useCallback(() => {
setShowSucceedInfo(false);
}, []);
const { title, field, editType, maxExecDuration, maxTrialNum, trialConcurrency, updateOverviewPage } = useContext(
EditExpeParamContext
);
let defaultVal = '';
let editVal = '';
if (title === 'Max duration') {
defaultVal = maxExecDuration;
editVal = maxExecDuration;
} else if (title === 'Max trial numbers') {
defaultVal = maxTrialNum.toString();
editVal = maxTrialNum.toString();
} else {
defaultVal = trialConcurrency.toString();
editVal = trialConcurrency.toString();
}
const [editInputVal, setEditValInput] = useState(editVal);
function setInputVal(event: any): void {
setEditValInput(event.target.value);
}
function cancelEdit(): void {
setEditValInput(defaultVal);
showPencil();
}
async function confirmEdit(): Promise<void> {
const isMaxDuration = title === 'Max duration';
const newProfile = Object.assign({}, EXPERIMENT.profile);
let beforeParam = '';
if (!isMaxDuration && !editInputVal.match(/^[1-9]\d*$/)) {
showMessageInfo('Please enter a positive integer!', 'error');
return;
}
if (isMaxDuration) {
beforeParam = maxExecDuration;
} else if (title === 'Max trial numbers') {
beforeParam = maxTrialNum.toString();
} else {
beforeParam = trialConcurrency.toString();
}
if (editInputVal === beforeParam) {
showMessageInfo(`Trial ${field} has not changed`, 'error');
return;
}
if (isMaxDuration) {
newProfile.params[field] = convertTimeToSecond(editInputVal);
} else {
newProfile.params[field] = parseInt(editInputVal, 10);
}
// rest api, modify trial concurrency value
try {
const res = await axios.put(`${MANAGER_IP}/experiment`, newProfile, {
// eslint-disable-next-line @typescript-eslint/camelcase
params: { update_type: editType }
});
if (res.status === 200) {
showMessageInfo(`Successfully updated ${field}`, 'success');
}
} catch (error) {
if (error.response && error.response.data.error) {
showMessageInfo(`Failed to update trial ${field}\n${error.response.data.error}`, 'error');
} else if (error.response) {
showMessageInfo(`Failed to update trial ${field}\nServer responsed ${error.response.status}`, 'error');
} else if (error.message) {
showMessageInfo(`Failed to update trial ${field}\n${error.message}`, 'error');
} else {
showMessageInfo(`Failed to update trial ${field}\nUnknown error`, 'error');
}
}
showPencil();
updateOverviewPage();
}
function showMessageInfo(info: string, typeInfo: string): any {
setInfo(info);
setTypeInfo(typeInfo);
showSucceedInfo();
setTimeout(hideSucceedInfo, 2000);
}
return (
<EditExpeParamContext.Consumer>
{(value): React.ReactNode => {
return (
<React.Fragment>
<p>{value.title}</p>
<div>
<input
className={`${value.field} durationInput`}
ref={DurationInputRef}
disabled={isShowPencil ? true : false}
value={editInputVal}
onChange={setInputVal}
/>
{isShowPencil && (
<span className='edit' onClick={hidePencil}>
{Edit}
</span>
)}
{!isShowPencil && (
<span className='series'>
<span className='confirm' onClick={confirmEdit}>
{CheckMark}
</span>
<span className='cancel' onClick={cancelEdit}>
{Cancel}
</span>
</span>
)}
{isShowSucceedInfo && <MessageInfo className='info' typeInfo={typeInfo} info={info} />}
</div>
</React.Fragment>
);
}}
</EditExpeParamContext.Consumer>
);
};
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