Skip to content
This repository has been archived by the owner on Sep 18, 2024. It is now read-only.

Commit

Permalink
Chevron icon before table row and TableList refactoring (#2900)
Browse files Browse the repository at this point in the history
  • Loading branch information
ultmaster authored Oct 14, 2020
1 parent 842f6cd commit 88a225f
Show file tree
Hide file tree
Showing 11 changed files with 870 additions and 1,064 deletions.
1 change: 1 addition & 0 deletions src/webui/.eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"@typescript-eslint/no-inferrable-types": 0,
"@typescript-eslint/no-use-before-define": [2, "nofunc"],
"@typescript-eslint/no-var-requires": 0,
"@typescript-eslint/no-unused-vars": [2, { "argsIgnorePattern": "^_" }],
"arrow-parens": [2, "as-needed"],
"no-inner-declarations": 0,
"no-empty": 2,
Expand Down
129 changes: 8 additions & 121 deletions src/webui/src/components/TrialsDetail.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
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 { Trial } from '../static/model/trial';
import { AppContext } from '../App';
import { Title } from './overview/Title';
import { TitleContext } from './overview/TitleContext';
import DefaultPoint from './trial-detail/DefaultMetricPoint';
import Duration from './trial-detail/Duration';
import Para from './trial-detail/Para';
Expand All @@ -13,18 +10,8 @@ import TableList from './trial-detail/TableList';
import '../static/style/trialsDetail.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 {
tablePageSize: number; // table components val
whichChart: string;
searchType: string;
searchFilter: (trial: Trial) => boolean;
}

class TrialsDetail extends React.Component<{}, TrialDetailState> {
Expand All @@ -39,71 +26,22 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> {
constructor(props) {
super(props);
this.state = {
tablePageSize: 20,
whichChart: 'Default metric',
searchType: 'id',
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/explicit-function-return-type
searchFilter: trial => true
whichChart: 'Default metric'
};
}

// 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 => {
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 {
const { tablePageSize, whichChart, searchType } = this.state;
const source = TRIALS.filter(this.state.searchFilter);
const trialIds = TRIALS.filter(this.state.searchFilter).map(trial => trial.id);
const { whichChart } = this.state;
const source = TRIALS.toArray();
const trialIds = TRIALS.toArray().map(trial => trial.id);

return (
<AppContext.Consumer>
{(value): React.ReactNode => (
{(_value): React.ReactNode => (
<React.Fragment>
<div className='trial' id='tabsty'>
<Pivot
Expand Down Expand Up @@ -144,61 +82,10 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> {
</Pivot>
</div>
{/* trial table list */}
<div className='bulletedList' style={{ marginTop: 18 }}>
<Stack className='title'>
<TitleContext.Provider value={{ text: 'Trial jobs', icon: 'BulletedList' }}>
<Title />
</TitleContext.Provider>
</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>
<div style={{ backgroundColor: '#fff' }}>
<TableList
pageSize={tablePageSize}
tableSource={source.map(trial => trial.tableRecord)}
columnList={value.columnList}
changeColumn={value.changeColumn}
tableSource={source}
trialsUpdateBroadcast={this.context.trialsUpdateBroadcast}
// TODO: change any to specific type
ref={(tabList): any => (this.tableList = tabList)}
/>
</div>
</React.Fragment>
Expand Down
129 changes: 48 additions & 81 deletions src/webui/src/components/modals/ChangeColumnComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,35 @@
import * as React from 'react';
import { Dialog, DialogType, DialogFooter, Checkbox, PrimaryButton, DefaultButton } from '@fluentui/react';
import { OPERATION } from '../../static/const';

interface ChangeColumnState {
userSelectColumnList: string[];
originSelectColumnList: string[];
// buffer, not saved yet
currentSelected: string[];
}

interface ChangeColumnProps {
isHideDialog: boolean;
showColumn: string[]; // all column List
selectedColumn: string[]; // user selected column list
changeColumn: (val: string[]) => void;
hideShowColumnDialog: () => void;
allColumns: SimpleColumn[]; // all column List
selectedColumns: string[]; // user selected column list
onSelectedChange: (val: string[]) => void;
onHideDialog: () => void;
minSelected?: number;
}

interface SimpleColumn {
key: string; // key for management
name: string; // name to display
}

interface CheckBoxItems {
label: string;
checked: boolean;
onChange: () => void;
}

class ChangeColumnComponent extends React.Component<ChangeColumnProps, ChangeColumnState> {
constructor(props: ChangeColumnProps) {
super(props);
this.state = {
userSelectColumnList: this.props.selectedColumn,
originSelectColumnList: this.props.selectedColumn
currentSelected: this.props.selectedColumns
};
}

Expand All @@ -38,110 +42,73 @@ class ChangeColumnComponent extends React.Component<ChangeColumnProps, ChangeCol
label: string,
val?: boolean
): void => {
const source: string[] = JSON.parse(JSON.stringify(this.state.userSelectColumnList));
const source: string[] = [...this.state.currentSelected];
if (val === true) {
if (!source.includes(label)) {
source.push(label);
this.setState(() => ({ userSelectColumnList: source }));
this.setState({ currentSelected: source });
}
} else {
if (source.includes(label)) {
// remove from source
const result = source.filter(item => item !== label);
this.setState(() => ({ userSelectColumnList: result }));
}
// remove from source
const result = source.filter(item => item !== label);
this.setState({ currentSelected: result });
}
};

saveUserSelectColumn = (): void => {
const { userSelectColumnList } = this.state;
const { showColumn } = this.props;
// sort by Trial No. | ID | Duration | Start Time | End Time | ...
const sortColumn: string[] = [];
/**
*
* 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();
const { currentSelected } = this.state;
const { allColumns, onSelectedChange } = this.props;
const selectedColumns = allColumns.map(column => column.key).filter(key => currentSelected.includes(key));
onSelectedChange(selectedColumns);
this.hideDialog();
};

// user exit dialog
cancelOption = (): void => {
// reset select column
const { originSelectColumnList } = this.state;
this.setState({ userSelectColumnList: originSelectColumnList }, () => {
this.setState({ currentSelected: this.props.selectedColumns }, () => {
this.hideDialog();
});
};

private hideDialog = (): void => {
this.props.onHideDialog();
};

render(): React.ReactNode {
const { showColumn, isHideDialog } = this.props;
const { userSelectColumnList } = 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) });
}
});
const { allColumns, minSelected } = this.props;
const { currentSelected } = this.state;
return (
<div>
<Dialog
hidden={isHideDialog} // required field!
hidden={false}
dialogContentProps={{
type: DialogType.largeHeader,
title: 'Change table column',
subText: 'You can chose which columns you want to see in the table.'
title: 'Customize columns',
subText: 'You can choose which columns you wish to see.'
}}
modalProps={{
isBlocking: false,
styles: { main: { maxWidth: 450 } }
}}
>
<div className='columns-height'>
{renderOptions.map(item => {
return <Checkbox key={item.label} {...item} styles={{ root: { marginBottom: 8 } }} />;
})}
{allColumns.map(item => (
<Checkbox
key={item.key}
label={item.name}
checked={currentSelected.includes(item.key)}
onChange={this.makeChangeHandler(item.key)}
styles={{ root: { marginBottom: 8 } }}
/>
))}
</div>
<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} />
</DialogFooter>
</Dialog>
Expand Down
Loading

0 comments on commit 88a225f

Please sign in to comment.