Unverified Commit c55300df authored by Lijiaoa's avatar Lijiaoa Committed by GitHub
Browse files

[webui] Add Expanded icon for table (#3103)

parent e9c62bac
...@@ -6,6 +6,7 @@ import NavCon from './components/NavCon'; ...@@ -6,6 +6,7 @@ import NavCon from './components/NavCon';
import MessageInfo from './components/modals/MessageInfo'; import MessageInfo from './components/modals/MessageInfo';
import { SlideNavBtns } from './components/slideNav/SlideNavBtns'; import { SlideNavBtns } from './components/slideNav/SlideNavBtns';
import './App.scss'; import './App.scss';
import './static/style/common.scss';
interface AppState { interface AppState {
interval: number; interval: number;
......
...@@ -168,8 +168,11 @@ class Overview extends React.Component<{}, OverviewState> { ...@@ -168,8 +168,11 @@ class Overview extends React.Component<{}, OverviewState> {
</Stack> </Stack>
<div className='overviewChart'> <div className='overviewChart'>
<Accuracy accuracyData={accuracyGraphData} accNodata={noDataMessage} /> <Accuracy accuracyData={accuracyGraphData} accNodata={noDataMessage} />
<SuccessTable
trialIds={bestTrials.map(trial => trial.info.trialJobId)}
updateOverviewPage={updateOverviewPage}
/>
</div> </div>
<SuccessTable trialIds={bestTrials.map(trial => trial.info.trialJobId)} />
</div> </div>
<div className='overviewCommand1'> <div className='overviewCommand1'>
<Command1 /> <Command1 />
......
...@@ -188,16 +188,16 @@ export const EditExperimentParam = (): any => { ...@@ -188,16 +188,16 @@ export const EditExperimentParam = (): any => {
/> />
)} )}
{isShowPencil && ( {isShowPencil && (
<span className='edit' onClick={hidePencil}> <span className='edit cursor' onClick={hidePencil}>
{Edit} {Edit}
</span> </span>
)} )}
{!isShowPencil && ( {!isShowPencil && (
<span className='series'> <span className='series'>
<span className='confirm' onClick={confirmEdit}> <span className='confirm cursor' onClick={confirmEdit}>
{CheckMark} {CheckMark}
</span> </span>
<span className='cancel' onClick={cancelEdit}> <span className='cancel cursor' onClick={cancelEdit}>
{Cancel} {Cancel}
</span> </span>
</span> </span>
......
import * as React from 'react';
import { DetailsRow, IDetailsRowBaseProps } from '@fluentui/react';
import OpenRow from '../../public-child/OpenRow';
interface DetailsProps {
detailsProps: IDetailsRowBaseProps;
}
interface DetailsState {
isExpand: boolean;
}
class Details extends React.Component<DetailsProps, DetailsState> {
constructor(props: DetailsProps) {
super(props);
this.state = { isExpand: false };
}
render(): React.ReactNode {
const { detailsProps } = this.props;
const { isExpand } = this.state;
return (
<div>
<div
onClick={(): void => {
this.setState(() => ({ isExpand: !isExpand }));
}}
>
<DetailsRow {...detailsProps} />
</div>
{isExpand && <OpenRow trialId={detailsProps.item.id} />}
</div>
);
}
}
export default Details;
...@@ -3,6 +3,8 @@ import { ...@@ -3,6 +3,8 @@ import {
DetailsList, DetailsList,
IDetailsListProps, IDetailsListProps,
IColumn, IColumn,
Icon,
DetailsRow,
IRenderFunction, IRenderFunction,
IDetailsHeaderProps, IDetailsHeaderProps,
Sticky, Sticky,
...@@ -11,9 +13,10 @@ import { ...@@ -11,9 +13,10 @@ import {
ScrollbarVisibility ScrollbarVisibility
} from '@fluentui/react'; } from '@fluentui/react';
import DefaultMetric from '../../public-child/DefaultMetric'; import DefaultMetric from '../../public-child/DefaultMetric';
import Details from './Details'; import OpenRow from '../../public-child/OpenRow';
import { convertDuration } from '../../../static/function'; import { convertDuration, copyAndSort } from '../../../static/function';
import { TRIALS } from '../../../static/datamodel'; import { TRIALS } from '../../../static/datamodel';
import { SortInfo } from '../../../static/interface';
import { DETAILTABS } from '../../stateless-component/NNItabs'; import { DETAILTABS } from '../../stateless-component/NNItabs';
import '../../../static/style/succTable.scss'; import '../../../static/style/succTable.scss';
import '../../../static/style/tableStatus.css'; import '../../../static/style/tableStatus.css';
...@@ -21,12 +24,15 @@ import '../../../static/style/openRow.scss'; ...@@ -21,12 +24,15 @@ import '../../../static/style/openRow.scss';
interface SuccessTableProps { interface SuccessTableProps {
trialIds: string[]; trialIds: string[];
// eslint-disable-next-line @typescript-eslint/no-unused-vars
updateOverviewPage: () => void;
} }
interface SuccessTableState { interface SuccessTableState {
columns: IColumn[]; columns: IColumn[];
source: Array<any>; source: Array<any>;
innerWidth: number; expandRowIdList: Set<string>;
sortInfo: SortInfo;
} }
class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState> { class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState> {
...@@ -35,18 +41,42 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState> ...@@ -35,18 +41,42 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState>
this.state = { this.state = {
columns: this.columns, columns: this.columns,
source: TRIALS.table(this.props.trialIds), source: TRIALS.table(this.props.trialIds),
innerWidth: window.innerWidth sortInfo: { field: '', isDescend: false },
expandRowIdList: new Set() // store expanded row's trial id
}; };
} }
private onRenderRow: IDetailsListProps['onRenderRow'] = props => { componentDidUpdate(prevProps: SuccessTableProps): void {
if (props) { if (this.props.trialIds !== prevProps.trialIds) {
return <Details detailsProps={props} />; const { trialIds } = this.props;
this.setState(() => ({ source: TRIALS.table(trialIds) }));
} }
return null; }
};
onColumnClick = (ev: React.MouseEvent<HTMLElement>, getColumn: IColumn): void => { render(): React.ReactNode {
const { columns, source, sortInfo } = this.state;
const keepSortedSource = copyAndSort(source, sortInfo.field, sortInfo.isDescend);
const isNoneData = source.length === 0 ? true : false;
return (
<div id='succTable'>
<ScrollablePane className='scrollPanel' scrollbarVisibility={ScrollbarVisibility.auto}>
<DetailsList
columns={columns}
items={keepSortedSource}
setKey='set'
compact={true}
onRenderRow={this.onRenderRow}
onRenderDetailsHeader={this.onRenderDetailsHeader}
selectionMode={0} // close selector function
className='succTable'
/>
</ScrollablePane>
{isNoneData && <div className='succTable-tooltip'>{this.tooltipStr}</div>}
</div>
);
}
private onColumnClick = (_ev: React.MouseEvent<HTMLElement>, getColumn: IColumn): void => {
const { columns, source } = this.state; const { columns, source } = this.state;
const newColumns: IColumn[] = columns.slice(); const newColumns: IColumn[] = columns.slice();
const currColumn: IColumn = newColumns.filter(item => getColumn.key === item.key)[0]; const currColumn: IColumn = newColumns.filter(item => getColumn.key === item.key)[0];
...@@ -60,32 +90,51 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState> ...@@ -60,32 +90,51 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState>
} }
}); });
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const newItems = this.copyAndSort(source, currColumn.fieldName!, currColumn.isSortedDescending); const newItems = copyAndSort(source, currColumn.fieldName!, currColumn.isSortedDescending);
this.setState({ this.setState({
columns: newColumns, columns: newColumns,
source: newItems source: newItems,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
sortInfo: { field: currColumn.fieldName!, isDescend: currColumn.isSortedDescending }
}); });
}; };
private copyAndSort<T>(items: T[], columnKey: string, isSortedDescending?: boolean): T[] { private tooltipStr = (
const key = columnKey as keyof T;
return items.slice(0).sort((a: T, b: T) => ((isSortedDescending ? a[key] < b[key] : a[key] > b[key]) ? 1 : -1));
}
tooltipStr = (
<React.Fragment> <React.Fragment>
The experiment is running, please wait for the final metric patiently. You could also find status of trial The experiment is running, please wait for the final metric patiently. You could also find status of trial
job with <span>{DETAILTABS}</span> button. job with <span>{DETAILTABS}</span> button.
</React.Fragment> </React.Fragment>
); );
columns = [ private columns = [
{
key: '_expand',
name: '',
onRender: (item: any): any => (
<Icon
aria-hidden={true}
iconName='ChevronRight'
styles={{
root: {
transition: 'all 0.2s',
transform: `rotate(${this.state.expandRowIdList.has(item.id) ? 90 : 0}deg)`
}
}}
className='cursor'
onClick={this.expandTrialId.bind(this, Event, item.id)}
/>
),
fieldName: 'expand',
isResizable: false,
minWidth: 20,
maxWidth: 20
},
{ {
name: 'Trial No.', name: 'Trial No.',
key: 'sequenceId', key: 'sequenceId',
fieldName: 'sequenceId', // required! fieldName: 'sequenceId', // required!
minWidth: 65, minWidth: 60,
maxWidth: 119, maxWidth: 100,
isResizable: true, isResizable: true,
data: 'number', data: 'number',
onColumnClick: this.onColumnClick, onColumnClick: this.onColumnClick,
...@@ -95,8 +144,8 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState> ...@@ -95,8 +144,8 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState>
name: 'ID', name: 'ID',
key: 'id', key: 'id',
fieldName: 'id', fieldName: 'id',
minWidth: 65, minWidth: 60,
maxWidth: 119, maxWidth: 118,
isResizable: true, isResizable: true,
className: 'tableHead leftTitle', className: 'tableHead leftTitle',
data: 'string', data: 'string',
...@@ -106,7 +155,7 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState> ...@@ -106,7 +155,7 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState>
{ {
name: 'Duration', name: 'Duration',
key: 'duration', key: 'duration',
minWidth: 90, minWidth: 85,
maxWidth: 166, maxWidth: 166,
isResizable: true, isResizable: true,
fieldName: 'duration', fieldName: 'duration',
...@@ -121,7 +170,7 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState> ...@@ -121,7 +170,7 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState>
{ {
name: 'Status', name: 'Status',
key: 'status', key: 'status',
minWidth: 108, minWidth: 98,
maxWidth: 160, maxWidth: 160,
isResizable: true, isResizable: true,
fieldName: 'status', fieldName: 'status',
...@@ -133,7 +182,7 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState> ...@@ -133,7 +182,7 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState>
name: 'Default metric', name: 'Default metric',
key: 'accuracy', key: 'accuracy',
fieldName: 'accuracy', fieldName: 'accuracy',
minWidth: 108, minWidth: 100,
maxWidth: 166, maxWidth: 166,
isResizable: true, isResizable: true,
data: 'number', data: 'number',
...@@ -142,7 +191,7 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState> ...@@ -142,7 +191,7 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState>
} }
]; ];
onRenderDetailsHeader: IRenderFunction<IDetailsHeaderProps> = (props, defaultRender) => { private onRenderDetailsHeader: IRenderFunction<IDetailsHeaderProps> = (props, defaultRender) => {
if (!props) { if (!props) {
return null; return null;
} }
...@@ -156,46 +205,35 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState> ...@@ -156,46 +205,35 @@ class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState>
); );
}; };
setInnerWidth = (): void => { private onRenderRow: IDetailsListProps['onRenderRow'] = props => {
this.setState(() => ({ innerWidth: window.innerWidth })); const { expandRowIdList } = this.state;
if (props) {
return (
<div>
<div>
<DetailsRow {...props} />
</div>
{Array.from(expandRowIdList).map(
item => item === props.item.id && <OpenRow key={item} trialId={item} />
)}
</div>
);
}
return null;
}; };
componentDidMount(): void { private expandTrialId = (_event: any, id: string): void => {
window.addEventListener('resize', this.setInnerWidth); const { expandRowIdList } = this.state;
} const { updateOverviewPage } = this.props;
componentWillUnmount(): void { const copyExpandList = expandRowIdList;
window.removeEventListener('resize', this.setInnerWidth); if (copyExpandList.has(id)) {
} copyExpandList.delete(id);
} else {
componentDidUpdate(prevProps: SuccessTableProps): void { copyExpandList.add(id);
if (this.props.trialIds !== prevProps.trialIds) {
const { trialIds } = this.props;
this.setState(() => ({ source: TRIALS.table(trialIds) }));
} }
} this.setState(() => ({ expandRowIdList: copyExpandList }));
updateOverviewPage();
render(): React.ReactNode { };
const { columns, source } = this.state;
const isNoneData = source.length === 0 ? true : false;
return (
<div id='succTable'>
<ScrollablePane className='scrollPanel' scrollbarVisibility={ScrollbarVisibility.auto}>
<DetailsList
columns={columns}
items={source}
setKey='set'
compact={true}
onRenderRow={this.onRenderRow}
onRenderDetailsHeader={this.onRenderDetailsHeader}
selectionMode={0} // close selector function
className='succTable'
/>
</ScrollablePane>
{isNoneData && <div className='succTable-tooltip'>{this.tooltipStr}</div>}
</div>
);
}
} }
export default SuccessTable; export default SuccessTable;
...@@ -15,8 +15,8 @@ import { ...@@ -15,8 +15,8 @@ import {
import React from 'react'; import React from 'react';
import { EXPERIMENT, TRIALS } from '../../static/datamodel'; import { EXPERIMENT, TRIALS } from '../../static/datamodel';
import { TOOLTIP_BACKGROUND_COLOR } from '../../static/const'; import { TOOLTIP_BACKGROUND_COLOR } from '../../static/const';
import { convertDuration, formatTimestamp } from '../../static/function'; import { convertDuration, formatTimestamp, copyAndSort } from '../../static/function';
import { TableObj } from '../../static/interface'; import { TableObj, SortInfo } from '../../static/interface';
import '../../static/style/search.scss'; import '../../static/style/search.scss';
import '../../static/style/tableStatus.css'; import '../../static/style/tableStatus.css';
import '../../static/style/logPath.scss'; import '../../static/style/logPath.scss';
...@@ -56,36 +56,6 @@ const searchOptionLiterals = { ...@@ -56,36 +56,6 @@ const searchOptionLiterals = {
const defaultDisplayedColumns = ['sequenceId', 'id', 'duration', 'status', 'latestAccuracy']; const defaultDisplayedColumns = ['sequenceId', 'id', 'duration', 'status', 'latestAccuracy'];
interface SortInfo {
field: string;
isDescend?: boolean;
}
function _copyAndSort<T>(items: T[], columnKey: string, isSortedDescending?: boolean): any {
const key = columnKey as keyof T;
return items.slice(0).sort(function(a: T, b: T): any {
if (
a[key] === undefined ||
Object.is(a[key], NaN) ||
Object.is(a[key], Infinity) ||
Object.is(a[key], -Infinity) ||
typeof a[key] === 'object'
) {
return 1;
}
if (
b[key] === undefined ||
Object.is(b[key], NaN) ||
Object.is(b[key], Infinity) ||
Object.is(b[key], -Infinity) ||
typeof b[key] === 'object'
) {
return -1;
}
return (isSortedDescending ? a[key] < b[key] : a[key] > b[key]) ? 1 : -1;
});
}
function _inferColumnTitle(columnKey: string): string { function _inferColumnTitle(columnKey: string): string {
if (columnKey === 'sequenceId') { if (columnKey === 'sequenceId') {
return 'Trial No.'; return 'Trial No.';
...@@ -238,7 +208,7 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -238,7 +208,7 @@ class TableList extends React.Component<TableListProps, TableListState> {
const { sortInfo } = this.state; const { sortInfo } = this.state;
if (sortInfo.field !== '') { if (sortInfo.field !== '') {
return _copyAndSort(items, sortInfo.field, sortInfo.isDescend); return copyAndSort(items, sortInfo.field, sortInfo.isDescend);
} else { } else {
return items; return items;
} }
...@@ -255,6 +225,7 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -255,6 +225,7 @@ class TableList extends React.Component<TableListProps, TableListState> {
<Icon <Icon
aria-hidden={true} aria-hidden={true}
iconName='ChevronRight' iconName='ChevronRight'
className='cursor'
styles={{ styles={{
root: { root: {
transition: 'all 0.2s', transition: 'all 0.2s',
......
...@@ -269,6 +269,30 @@ function caclMonacoEditorHeight(height): number { ...@@ -269,6 +269,30 @@ function caclMonacoEditorHeight(height): number {
return height - 178; return height - 178;
} }
function copyAndSort<T>(items: T[], columnKey: string, isSortedDescending?: boolean): any {
const key = columnKey as keyof T;
return items.slice(0).sort(function(a: T, b: T): any {
if (
a[key] === undefined ||
Object.is(a[key], NaN) ||
Object.is(a[key], Infinity) ||
Object.is(a[key], -Infinity) ||
typeof a[key] === 'object'
) {
return 1;
}
if (
b[key] === undefined ||
Object.is(b[key], NaN) ||
Object.is(b[key], Infinity) ||
Object.is(b[key], -Infinity) ||
typeof b[key] === 'object'
) {
return -1;
}
return (isSortedDescending ? a[key] < b[key] : a[key] > b[key]) ? 1 : -1;
});
}
export { export {
convertTime, convertTime,
convertDuration, convertDuration,
...@@ -288,5 +312,6 @@ export { ...@@ -288,5 +312,6 @@ export {
requestAxios, requestAxios,
isNaNorInfinity, isNaNorInfinity,
formatComplexTypeValue, formatComplexTypeValue,
caclMonacoEditorHeight caclMonacoEditorHeight,
copyAndSort
}; };
...@@ -212,6 +212,12 @@ interface EventMap { ...@@ -212,6 +212,12 @@ interface EventMap {
[key: string]: () => void; [key: string]: () => void;
} }
// table column sort
interface SortInfo {
field: string;
isDescend?: boolean;
}
export { export {
TableObj, TableObj,
TableRecord, TableRecord,
...@@ -233,5 +239,6 @@ export { ...@@ -233,5 +239,6 @@ export {
NNIManagerStatus, NNIManagerStatus,
EventMap, EventMap,
SingleAxis, SingleAxis,
MultipleAxes MultipleAxes,
SortInfo
}; };
.cursor{
&:hover, & i:hover{
cursor: pointer;
}
}
\ No newline at end of file
...@@ -3,10 +3,6 @@ $seriesIconMargin: 10px; ...@@ -3,10 +3,6 @@ $seriesIconMargin: 10px;
.ExpDuration { .ExpDuration {
margin-top: 20px; margin-top: 20px;
span:hover {
cursor: pointer;
}
.maxTrialNum { .maxTrialNum {
margin-bottom: 10px; margin-bottom: 10px;
} }
......
...@@ -64,8 +64,3 @@ ...@@ -64,8 +64,3 @@
.ms-ProgressIndicator-itemProgress { .ms-ProgressIndicator-itemProgress {
padding: 0; padding: 0;
} }
.cursor,
.cursor:hover {
cursor: pointer;
}
...@@ -30,4 +30,4 @@ $tableHeight: 381px; ...@@ -30,4 +30,4 @@ $tableHeight: 381px;
padding-left: 6px; padding-left: 6px;
box-sizing: border-box; box-sizing: border-box;
} }
} }
\ No newline at end of file
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