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

support searching customized string in the search box (#4823)

parent e8dc4562
...@@ -6,6 +6,13 @@ import { AppContext } from '@/App'; ...@@ -6,6 +6,13 @@ import { AppContext } from '@/App';
import { getSearchInputValueBySearchList } from './searchFunction'; import { getSearchInputValueBySearchList } from './searchFunction';
// This file is for search trial ['Trial id', 'Trial No.'] // This file is for search trial ['Trial id', 'Trial No.']
// you could use `filter` button -> `Trial id` or `Trial No.` to input value
// if you want to input something in the search input, please follow this fomat
// Trial id:r; // filter all trials that trial id include `r`
// Trial No.:1; // only filter one trial that trial no is 1.
// And if you want to filter trials id|No by using simple function, you could use this:(nni version >= 2.8 support simple search)
// 1 // only filter one trial that trial no is 1
// bw // only filter that trial id include `bw`
function GeneralSearch(props): any { function GeneralSearch(props): any {
const { updateDetailPage } = useContext(AppContext); const { updateDetailPage } = useContext(AppContext);
......
...@@ -12,6 +12,7 @@ import { EXPERIMENT } from '@static/datamodel'; ...@@ -12,6 +12,7 @@ import { EXPERIMENT } from '@static/datamodel';
import { SearchItems } from '@static/interface'; import { SearchItems } from '@static/interface';
import SearchParameterConditions from './SearchParameterConditions'; import SearchParameterConditions from './SearchParameterConditions';
import GeneralSearch from './GeneralSearch'; import GeneralSearch from './GeneralSearch';
import SearchDefaultMetric from './SearchDefaultMetric';
import { classNames, isChoiceType } from './searchFunction'; import { classNames, isChoiceType } from './searchFunction';
import { AppContext } from '@/App'; import { AppContext } from '@/App';
...@@ -61,6 +62,21 @@ function Search(props): any { ...@@ -61,6 +62,21 @@ function Search(props): any {
}); });
}); });
menu.push({
key: 'Default metric',
text: 'Default metric',
subMenuProps: {
items: [
{
key: 'Default metric',
text: 'Default metric',
// component: SearchParameterConditions.tsx
onRender: renderDefaultMetricSearchComponent.bind('Default metric')
}
]
}
});
const filterMenu: IContextualMenuProps = { const filterMenu: IContextualMenuProps = {
shouldFocusOnMount: true, shouldFocusOnMount: true,
directionalHint: DirectionalHint.bottomLeftEdge, directionalHint: DirectionalHint.bottomLeftEdge,
...@@ -89,6 +105,19 @@ function Search(props): any { ...@@ -89,6 +105,19 @@ function Search(props): any {
); );
} }
function renderDefaultMetricSearchComponent(item: IContextualMenuItem, dismissMenu: () => void): JSX.Element {
return (
<SearchDefaultMetric
parameter={item.text}
searchFilter={searchFilter} // search filter list
changeSearchFilterList={changeSearchFilterList}
updatePage={updateDetailPage}
setSearchInputVal={setSearchInputVal}
dismiss={dismissMenu} // close menu
/>
);
}
function renderIdAndNoComponent(item: IContextualMenuItem, dismissMenu: () => void): JSX.Element { function renderIdAndNoComponent(item: IContextualMenuItem, dismissMenu: () => void): JSX.Element {
return ( return (
<GeneralSearch <GeneralSearch
...@@ -142,102 +171,117 @@ function Search(props): any { ...@@ -142,102 +171,117 @@ function Search(props): any {
alert('Please delete special characters in the conditions!'); alert('Please delete special characters in the conditions!');
return; return;
} }
// according [input val] to change searchFilter list
const allFilterConditions = searchInputVal.trim().split(';');
const newSearchFilter: any = []; const newSearchFilter: any = [];
const str = searchInputVal.trim();
// user input: string and this string don't include [: < > ≠]
if (str.includes('>') || str.includes('<') || str.includes(':') || str.includes('')) {
// according [input val] to change searchFilter list
const allFilterConditions = searchInputVal.trim().split(';');
allFilterConditions.forEach(eachFilterConditionStr => {
eachFilterConditionStr = eachFilterConditionStr.trim();
// input content looks like that: `Trial id:`
if (
eachFilterConditionStr.endsWith(':') ||
eachFilterConditionStr.endsWith('<') ||
eachFilterConditionStr.endsWith('>') ||
eachFilterConditionStr.endsWith('')
) {
return;
} else {
let eachFilterConditionArr: string[] = [];
// delete '' in filter list // EXPERIMENT.searchSpace[parameter]._type === 'choice'
if (allFilterConditions.includes('')) { if (eachFilterConditionStr.includes('>') || eachFilterConditionStr.includes('<')) {
allFilterConditions.splice( const operator = eachFilterConditionStr.includes('>') === true ? '>' : '<';
allFilterConditions.findIndex(item => item === ''), eachFilterConditionArr = eachFilterConditionStr.trim().split(operator);
1 newSearchFilter.push({
); name: eachFilterConditionArr[0],
} operator: operator,
value1: eachFilterConditionArr[1].trim(),
allFilterConditions.forEach(eachFilterConditionStr => { value2: '',
// input content looks like that: `Trial id:` choice: [],
if ( isChoice: false
eachFilterConditionStr.endsWith(':') || });
eachFilterConditionStr.endsWith('<') || } else if (eachFilterConditionStr.includes('')) {
eachFilterConditionStr.endsWith('>') || // drop_rate≠6; status≠[x,xx,xxx]; conv_size≠[3,7]
eachFilterConditionStr.endsWith('') eachFilterConditionArr = eachFilterConditionStr.trim().split('');
) { const filterName =
return; eachFilterConditionArr[0] === 'Status' ? 'StatusNNI' : eachFilterConditionArr[0];
} else { const isChoicesType = isChoiceType(filterName);
let eachFilterConditionArr: string[] = []; newSearchFilter.push({
name: filterName,
// EXPERIMENT.searchSpace[parameter]._type === 'choice' operator: '',
if (eachFilterConditionStr.includes('>' || '<')) { value1: isChoicesType ? '' : JSON.parse(eachFilterConditionArr[1].trim()),
const operator = eachFilterConditionStr.includes('>') === true ? '>' : '<'; value2: '',
eachFilterConditionArr = eachFilterConditionStr.trim().split(operator); choice: isChoicesType ? convertStringArrToList(eachFilterConditionArr[1]) : [],
newSearchFilter.push({ isChoice: isChoicesType ? true : false
name: eachFilterConditionArr[0], });
operator: operator, } else if (eachFilterConditionStr.includes(':')) {
value1: eachFilterConditionArr[1].trim(), // = : conv_size:[1,2,3,4]; Trial id:3; hidden_size:[1,2], status:[val1,val2,val3]
value2: '', eachFilterConditionArr = eachFilterConditionStr.trim().split(':');
choice: [], const filterName =
isChoice: false eachFilterConditionArr[0] === 'Status' ? 'StatusNNI' : eachFilterConditionArr[0];
}); const isChoicesType = isChoiceType(filterName);
} else if (eachFilterConditionStr.includes('')) { const isArray =
// drop_rate≠6; status≠[x,xx,xxx]; conv_size≠[3,7] eachFilterConditionArr.length > 1 &&
eachFilterConditionArr = eachFilterConditionStr.trim().split(''); (eachFilterConditionArr[1].includes('[') || eachFilterConditionArr[1].includes(']'))
const filterName = eachFilterConditionArr[0] === 'Status' ? 'StatusNNI' : eachFilterConditionArr[0]; ? true
const isChoicesType = isChoiceType(filterName); : false;
newSearchFilter.push({ if (isArray === true) {
name: filterName, if (isChoicesType === true) {
operator: '', // status:[SUCCEEDED]
value1: isChoicesType ? '' : JSON.parse(eachFilterConditionArr[1].trim()), newSearchFilter.push({
value2: '', name: filterName,
choice: isChoicesType ? convertStringArrToList(eachFilterConditionArr[1]) : [], operator: '=',
isChoice: isChoicesType ? true : false value1: '',
}); value2: '',
} else if (eachFilterConditionStr.includes(':')) { choice: convertStringArrToList(eachFilterConditionArr[1]),
// = : conv_size:[1,2,3,4]; Trial id:3; hidden_size:[1,2], status:[val1,val2,val3] isChoice: true
eachFilterConditionArr = eachFilterConditionStr.trim().split(':'); });
const filterName = eachFilterConditionArr[0] === 'Status' ? 'StatusNNI' : eachFilterConditionArr[0]; } else {
const isChoicesType = isChoiceType(filterName); // drop_rate:[1,10]
const isArray = newSearchFilter.push({
eachFilterConditionArr.length > 1 && eachFilterConditionArr[1].includes('[' || ']') name: eachFilterConditionArr[0],
? true operator: 'between',
: false; value1: JSON.parse(eachFilterConditionArr[1].trim())[0],
if (isArray === true) { value2: JSON.parse(eachFilterConditionArr[1].trim())[1],
if (isChoicesType === true) { choice: [],
// status:[SUCCEEDED] isChoice: false
newSearchFilter.push({ });
name: filterName, }
operator: '=',
value1: '',
value2: '',
choice: convertStringArrToList(eachFilterConditionArr[1]),
isChoice: true
});
} else { } else {
// drop_rate:[1,10]
newSearchFilter.push({ newSearchFilter.push({
name: eachFilterConditionArr[0], name: eachFilterConditionArr[0],
operator: 'between', operator: '=',
value1: JSON.parse(eachFilterConditionArr[1].trim())[0], value1: eachFilterConditionArr[1].trim(),
value2: JSON.parse(eachFilterConditionArr[1].trim())[1], value2: '',
choice: [], choice: [],
isChoice: false isChoice: false
}); });
} }
} else { } else {
newSearchFilter.push({ return;
name: eachFilterConditionArr[0],
operator: '=',
value1: eachFilterConditionArr[1].trim(),
value2: '',
choice: [],
isChoice: false
});
} }
} else {
// user input: Trial id
return;
} }
});
} else {
const re = /^[0-9]+.?[0-9]*/;
if (re.test(str)) {
newSearchFilter.push({
name: 'Trial No.',
value1: str,
isChoice: false
});
} else {
newSearchFilter.push({
name: 'Trial id',
value1: str,
isChoice: false
});
} }
}); }
console.info(newSearchFilter); // eslint-disable-line
changeTableListPage(newSearchFilter); changeTableListPage(newSearchFilter);
} }
......
import React, { useState, useContext } from 'react';
import PropTypes from 'prop-types';
import { Stack, PrimaryButton, Dropdown, IDropdownOption } from '@fluentui/react';
import { EXPERIMENT } from '@static/datamodel';
import { getSearchInputValueBySearchList } from './searchFunction';
import { gap10 } from '@components/fluent/ChildrenGap';
import { AppContext } from '@/App';
// This file is for filtering trial default metric column including intermediate results
// you could click `filter` button -> `Default metric` use it
// if you want to use search input , please use these format:
// Default metric>0.9; // search trial metric value > 0.9
// Default metric<0.9; // search trial metric value < 0.9
// Default metric:[0.1,0.2]; // 0.1 < trial metric < 0.2
// Default metric:0.1009; // trial metric = 0.1009, because shown metric is dealed with,so it's no use in most time
function SearchDefaultMetric(props): any {
const { parameter, searchFilter, dismiss, changeSearchFilterList, setSearchInputVal } = props;
const { updateDetailPage } = useContext(AppContext);
const operatorList = ['between', '>', '<', '='];
const initValueList = getInitVal();
const [operatorVal, setOperatorVal] = useState(initValueList[0]);
const [firstInputVal, setFirstInputVal] = useState(initValueList[1] as string);
const [secondInputVal, setSecondInputVal] = useState(initValueList[2] as string);
function getInitVal(): Array<string | string[]> {
// push value: operator, firstInputVal(value1), secondInputVal(value2), choiceValue
const str: Array<string | string[]> = [];
if (searchFilter.length > 0) {
const filterElement = searchFilter.find(ele => ele.name === parameter);
if (filterElement !== undefined) {
str.push(filterElement.operator, filterElement.value1.toString(), filterElement.value2.toString());
} else {
// set init value
str.push('between', '', '', [] as string[]);
}
} else {
str.push('between', '', '', [] as string[]);
}
return str;
}
function updateOperatorDropdown(_event: React.FormEvent<HTMLDivElement>, item: IDropdownOption | undefined): void {
if (item !== undefined) {
setOperatorVal(item.key.toString());
}
}
function updateFirstInputVal(ev: React.ChangeEvent<HTMLInputElement>): void {
setFirstInputVal(ev.target.value);
}
function updateSecondInputVal(ev: React.ChangeEvent<HTMLInputElement>): void {
setSecondInputVal(ev.target.value);
}
function getSecondInputVal(): string {
if (secondInputVal === '' && operatorVal === 'between') {
// if user uses 'between' operator and doesn't write the second input value,
// help to set second value as this parameter max value
return EXPERIMENT.searchSpace[parameter]._value[1].toString();
}
return secondInputVal as string;
}
// click Apply button
function startFilterTrials(): void {
if (firstInputVal === '') {
alert('Please input related value!');
return;
}
if (firstInputVal.match(/[a-zA-Z]/) || secondInputVal.match(/[a-zA-Z]/)) {
alert('Please input a number!');
return;
}
let newSearchFilters = JSON.parse(JSON.stringify(searchFilter));
const find = newSearchFilters.filter(ele => ele.name === parameter);
if (find.length > 0) {
newSearchFilters = newSearchFilters.filter(item => item.name !== parameter);
} else {
newSearchFilters.push({
name: parameter,
operator: operatorVal,
value1: firstInputVal,
value2: getSecondInputVal(),
isChoice: false
});
}
setSearchInputVal(getSearchInputValueBySearchList(newSearchFilters));
changeSearchFilterList(newSearchFilters);
updateDetailPage();
dismiss(); // close menu
}
return (
<Stack horizontal className='filterConditions' tokens={gap10}>
<Dropdown
selectedKey={operatorVal}
options={operatorList.map(item => ({
key: item,
text: item
}))}
onChange={updateOperatorDropdown}
className='btn-vertical-middle'
styles={{ root: { width: 100 } }}
/>
{operatorVal === 'between' ? (
<div>
<input
type='text'
className='input input-padding'
onChange={updateFirstInputVal}
value={firstInputVal}
/>
<span className='and'>and</span>
<input
type='text'
className='input input-padding'
onChange={updateSecondInputVal}
value={secondInputVal}
/>
</div>
) : (
<input
type='text'
className='input input-padding'
onChange={updateFirstInputVal}
value={firstInputVal}
/>
)}
<PrimaryButton text='Apply' className='btn-vertical-middle' onClick={startFilterTrials} />
</Stack>
);
}
SearchDefaultMetric.propTypes = {
parameter: PropTypes.string,
searchFilter: PropTypes.array,
dismiss: PropTypes.func,
setSearchInputVal: PropTypes.func,
changeSearchFilterList: PropTypes.func,
updatePage: PropTypes.func
};
export default SearchDefaultMetric;
...@@ -6,7 +6,29 @@ import { getDropdownOptions, getSearchInputValueBySearchList } from './searchFun ...@@ -6,7 +6,29 @@ import { getDropdownOptions, getSearchInputValueBySearchList } from './searchFun
import { gap10 } from '@components/fluent/ChildrenGap'; import { gap10 } from '@components/fluent/ChildrenGap';
import { AppContext } from '@/App'; import { AppContext } from '@/App';
// This file is for filtering trial parameters and trial status /***
* This file is for filtering trial parameters and trial status
* filter status
* rules: (1) click filter -> click `Status` -> choose you wanted status, and click apply
* rules: (2) you could input the rule in the search input
* Status:[FAILED]; Status:[FAILED,SUCCEEDED]; Status≠[FAILED]; Status≠[FAILED,SUCCEEDED];
*
* filter parameters
* parameters have many types, such as int, string
* int: dropout_rate:[0.1,0.2]; // 0.1 < dropout_rate value < 0.2
* dropout_rate>0.5; // dropout_rate value > 0.5
* dropout_rate<0.6; // dropout_rate value < 0.6
*
* string: conv_size:[2]; // conv_size value is 2
* conv_size:2; // conv_size value is 2(more simple)
* conv_size:[2,3]; // conv_size value is 2 or 3
* conv_size≠[2,3]; // conv_size value is not 2 or 3
* conv_size≠[2]; // conv_size value is not 2
*
* wrong write:
* conv_size≠2; // wrong write, please don't input this!
* hidden_size≠1024 // please don't input this format!
*/
function SearchParameterConditions(props): any { function SearchParameterConditions(props): any {
const { parameter, searchFilter, dismiss, changeSearchFilterList, setSearchInputVal } = props; const { parameter, searchFilter, dismiss, changeSearchFilterList, setSearchInputVal } = props;
......
import { mergeStyleSets } from '@fluentui/react'; import { mergeStyleSets } from '@fluentui/react';
import { trialJobStatus } from '@static/const'; import { trialJobStatus } from '@static/const';
import { EXPERIMENT } from '@static/datamodel'; import { EXPERIMENT } from '@static/datamodel';
import { TableObj, SearchItems } from '@static/interface'; import { TableRecord, SearchItems } from '@static/interface';
const classNames = mergeStyleSets({ const classNames = mergeStyleSets({
menu: { menu: {
...@@ -77,10 +77,10 @@ const convertParametersValue = (searchItems: SearchItems[], relation: Map<string ...@@ -77,10 +77,10 @@ const convertParametersValue = (searchItems: SearchItems[], relation: Map<string
}; };
// relation: trial parameter -> type {conv_size -> number} // relation: trial parameter -> type {conv_size -> number}
const getTrialsBySearchFilters = ( const getTrialsBySearchFilters = (
arr: TableObj[], arr: TableRecord[],
searchItems: SearchItems[], searchItems: SearchItems[],
relation: Map<string, string> relation: Map<string, string>
): TableObj[] => { ): TableRecord[] => {
const que = convertParametersValue(searchItems, relation); const que = convertParametersValue(searchItems, relation);
// start to filter data by ['Trial id', 'Trial No.', 'Status'] [...parameters]... // start to filter data by ['Trial id', 'Trial No.', 'Status'] [...parameters]...
que.forEach(element => { que.forEach(element => {
...@@ -90,6 +90,20 @@ const getTrialsBySearchFilters = ( ...@@ -90,6 +90,20 @@ const getTrialsBySearchFilters = (
arr = arr.filter(trial => trial.sequenceId.toString() === element.value1); arr = arr.filter(trial => trial.sequenceId.toString() === element.value1);
} else if (element.name === 'StatusNNI') { } else if (element.name === 'StatusNNI') {
arr = searchChoiceFilter(arr, element, 'status'); arr = searchChoiceFilter(arr, element, 'status');
} else if (element.name === 'Default metric') {
if (element.operator === '=') {
arr = arr.filter(trial => trial.latestAccuracy! === JSON.parse(element.value1));
} else if (element.operator === '>') {
arr = arr.filter(trial => trial.latestAccuracy! > JSON.parse(element.value1));
} else if (element.operator === '<') {
arr = arr.filter(trial => trial.latestAccuracy! < JSON.parse(element.value1));
} else if (element.operator === 'between') {
arr = arr.filter(
trial =>
trial.latestAccuracy! > JSON.parse(element.value1) &&
trial.latestAccuracy! < JSON.parse(element.value2)
);
}
} else { } else {
const parameter = `space/${element.name}`; const parameter = `space/${element.name}`;
...@@ -116,8 +130,8 @@ const getTrialsBySearchFilters = ( ...@@ -116,8 +130,8 @@ const getTrialsBySearchFilters = (
}; };
// isChoice = true: status and trial parameters // isChoice = true: status and trial parameters
function findTrials(arr: TableObj[], choice: string[], filed: string): TableObj[] { function findTrials(arr: TableRecord[], choice: string[], filed: string): TableRecord[] {
const newResult: TableObj[] = []; const newResult: TableRecord[] = [];
const parameter = filed === 'status' ? 'status' : `space/${filed}`; const parameter = filed === 'status' ? 'status' : `space/${filed}`;
arr.forEach(trial => { arr.forEach(trial => {
choice.forEach(item => { choice.forEach(item => {
...@@ -130,7 +144,7 @@ function findTrials(arr: TableObj[], choice: string[], filed: string): TableObj[ ...@@ -130,7 +144,7 @@ function findTrials(arr: TableObj[], choice: string[], filed: string): TableObj[
return newResult; return newResult;
} }
function searchChoiceFilter(arr: TableObj[], element: SearchItems, field: string): TableObj[] { function searchChoiceFilter(arr: TableRecord[], element: SearchItems, field: string): TableRecord[] {
if (element.operator === '=') { if (element.operator === '=') {
return findTrials(arr, element.choice, field); return findTrials(arr, element.choice, field);
} else { } else {
......
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