Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
OpenDAS
nni
Commits
88a225f8
Unverified
Commit
88a225f8
authored
Oct 14, 2020
by
Yuge Zhang
Committed by
GitHub
Oct 14, 2020
Browse files
Chevron icon before table row and TableList refactoring (#2900)
parent
842f6cdb
Changes
11
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
870 additions
and
1064 deletions
+870
-1064
src/webui/.eslintrc
src/webui/.eslintrc
+1
-0
src/webui/src/components/TrialsDetail.tsx
src/webui/src/components/TrialsDetail.tsx
+8
-121
src/webui/src/components/modals/ChangeColumnComponent.tsx
src/webui/src/components/modals/ChangeColumnComponent.tsx
+48
-81
src/webui/src/components/modals/Compare.tsx
src/webui/src/components/modals/Compare.tsx
+143
-152
src/webui/src/components/public-child/ExpandableDetails.tsx
src/webui/src/components/public-child/ExpandableDetails.tsx
+22
-0
src/webui/src/components/public-child/PaginationTable.tsx
src/webui/src/components/public-child/PaginationTable.tsx
+120
-0
src/webui/src/components/trial-detail/Para.tsx
src/webui/src/components/trial-detail/Para.tsx
+51
-6
src/webui/src/components/trial-detail/TableList.tsx
src/webui/src/components/trial-detail/TableList.tsx
+465
-698
src/webui/src/static/interface.ts
src/webui/src/static/interface.ts
+1
-0
src/webui/src/static/model/trial.ts
src/webui/src/static/model/trial.ts
+10
-5
src/webui/src/static/style/table.scss
src/webui/src/static/style/table.scss
+1
-1
No files found.
src/webui/.eslintrc
View file @
88a225f8
...
@@ -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,
...
...
src/webui/src/components/TrialsDetail.tsx
View file @
88a225f8
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
{
Title
}
from
'
./overview/Title
'
;
import
{
TitleContext
}
from
'
./overview/TitleContext
'
;
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
'
;
...
@@ -13,18 +10,8 @@ import TableList from './trial-detail/TableList';
...
@@ -13,18 +10,8 @@ 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
>
{
...
@@ -39,71 +26,22 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> {
...
@@ -39,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
...
@@ -144,61 +82,10 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> {
...
@@ -144,61 +82,10 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> {
</
Pivot
>
</
Pivot
>
</
div
>
</
div
>
{
/* trial table list */
}
{
/* trial table list */
}
<
div
className
=
'bulletedList'
style
=
{
{
marginTop
:
18
}
}
>
<
div
style
=
{
{
backgroundColor
:
'
#fff
'
}
}
>
<
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
>
<
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
>
...
...
src/webui/src/components/modals/ChangeColumnComponent.tsx
View file @
88a225f8
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
:
'
C
hange tabl
e column
'
,
title
:
'
C
ustomiz
e column
s
'
,
subText
:
'
You can chose which columns you w
ant
to see
in the table
.
'
subText
:
'
You can cho
o
se which columns you w
ish
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
>
...
...
src/webui/src/components/modals/Compare.tsx
View file @
88a225f8
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
)
{
i
f
(
parameterList
.
length
>
0
)
{
i
ntersection
.
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
=
i
dList
.
length
>
3
?
'
flex
'
:
''
;
scrollClass
=
i
tems
.
length
>
3
?
'
flex
'
:
''
;
}
else
if
(
width
<
700
)
{
}
else
if
(
width
<
700
)
{
scrollClass
=
i
dList
.
length
>
1
?
'
flex
'
:
''
;
scrollClass
=
i
tems
.
length
>
1
?
'
flex
'
:
''
;
}
else
{
}
else
{
scrollClass
=
i
dList
.
length
>
2
?
'
flex
'
:
''
;
scrollClass
=
i
tems
.
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
.
_
intermediate
s
(
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
>
);
);
...
...
src/webui/src/components/public-child/ExpandableDetails.tsx
0 → 100644
View file @
88a225f8
import
*
as
React
from
'
react
'
;
import
{
DetailsRow
,
IDetailsRowBaseProps
}
from
'
@fluentui/react
'
;
import
OpenRow
from
'
../public-child/OpenRow
'
;
interface
ExpandableDetailsProps
{
detailsProps
:
IDetailsRowBaseProps
;
isExpand
:
boolean
;
}
class
ExpandableDetails
extends
React
.
Component
<
ExpandableDetailsProps
,
{}
>
{
render
():
React
.
ReactNode
{
const
{
detailsProps
,
isExpand
}
=
this
.
props
;
return
(
<
div
>
<
DetailsRow
{
...
detailsProps
}
/>
{
isExpand
&&
<
OpenRow
trialId
=
{
detailsProps
.
item
.
id
}
/>
}
</
div
>
);
}
}
export
default
ExpandableDetails
;
src/webui/src/components/public-child/PaginationTable.tsx
0 → 100644
View file @
88a225f8
import
*
as
React
from
'
react
'
;
import
{
DetailsList
,
Dropdown
,
Icon
,
IDetailsListProps
,
IDropdownOption
,
IStackTokens
,
Stack
}
from
'
@fluentui/react
'
;
import
ReactPaginate
from
'
react-paginate
'
;
interface
PaginationTableState
{
itemsPerPage
:
number
;
currentPage
:
number
;
itemsOnPage
:
any
[];
// this needs to be stored in state to prevent re-rendering
}
const
horizontalGapStackTokens
:
IStackTokens
=
{
childrenGap
:
20
,
padding
:
10
};
function
_currentTableOffset
(
perPage
:
number
,
currentPage
:
number
,
source
:
any
[]):
number
{
return
perPage
===
-
1
?
0
:
Math
.
min
(
currentPage
,
Math
.
floor
((
source
.
length
-
1
)
/
perPage
))
*
perPage
;
}
function
_obtainPaginationSlice
(
perPage
:
number
,
currentPage
:
number
,
source
:
any
[]):
any
[]
{
if
(
perPage
===
-
1
)
{
return
source
;
}
else
{
const
offset
=
_currentTableOffset
(
perPage
,
currentPage
,
source
);
return
source
.
slice
(
offset
,
offset
+
perPage
);
}
}
class
PaginationTable
extends
React
.
PureComponent
<
IDetailsListProps
,
PaginationTableState
>
{
constructor
(
props
:
IDetailsListProps
)
{
super
(
props
);
this
.
state
=
{
itemsPerPage
:
20
,
currentPage
:
0
,
itemsOnPage
:
[]
};
}
private
_onItemsPerPageSelect
(
event
:
React
.
FormEvent
<
HTMLDivElement
>
,
item
:
IDropdownOption
|
undefined
):
void
{
if
(
item
!==
undefined
)
{
const
{
items
}
=
this
.
props
;
// use current offset to calculate the next `current_page`
const
currentOffset
=
_currentTableOffset
(
this
.
state
.
itemsPerPage
,
this
.
state
.
currentPage
,
items
);
const
itemsPerPage
=
item
.
key
as
number
;
const
currentPage
=
Math
.
floor
(
currentOffset
/
itemsPerPage
);
this
.
setState
({
itemsPerPage
:
itemsPerPage
,
currentPage
:
currentPage
,
itemsOnPage
:
_obtainPaginationSlice
(
itemsPerPage
,
currentPage
,
this
.
props
.
items
)
});
}
}
private
_onPageSelect
(
event
:
any
):
void
{
const
currentPage
=
event
.
selected
;
this
.
setState
({
currentPage
:
currentPage
,
itemsOnPage
:
_obtainPaginationSlice
(
this
.
state
.
itemsPerPage
,
currentPage
,
this
.
props
.
items
)
});
}
componentDidUpdate
(
prevProps
:
IDetailsListProps
):
void
{
if
(
prevProps
.
items
!==
this
.
props
.
items
)
{
this
.
setState
({
itemsOnPage
:
_obtainPaginationSlice
(
this
.
state
.
itemsPerPage
,
this
.
state
.
currentPage
,
this
.
props
.
items
)
});
}
}
render
():
React
.
ReactNode
{
const
{
itemsPerPage
,
itemsOnPage
}
=
this
.
state
;
const
detailListProps
=
{
...
this
.
props
,
items
:
itemsOnPage
};
const
itemsCount
=
this
.
props
.
items
.
length
;
const
pageCount
=
itemsPerPage
===
-
1
?
1
:
Math
.
ceil
(
itemsCount
/
itemsPerPage
);
const
perPageOptions
=
[
{
key
:
10
,
text
:
'
10 items per page
'
},
{
key
:
20
,
text
:
'
20 items per page
'
},
{
key
:
50
,
text
:
'
50 items per page
'
},
{
key
:
-
1
,
text
:
'
All items
'
}
];
return
(
<
div
>
<
DetailsList
{
...
detailListProps
}
/>
<
Stack
horizontal
horizontalAlign
=
'end'
verticalAlign
=
'baseline'
styles
=
{
{
root
:
{
padding
:
10
}
}
}
tokens
=
{
horizontalGapStackTokens
}
>
<
Dropdown
selectedKey
=
{
itemsPerPage
}
options
=
{
perPageOptions
}
onChange
=
{
this
.
_onItemsPerPageSelect
.
bind
(
this
)
}
styles
=
{
{
dropdown
:
{
width
:
150
}
}
}
/>
<
ReactPaginate
previousLabel
=
{
<
Icon
aria
-
hidden
=
{
true
}
iconName
=
'ChevronLeft'
/>
}
nextLabel
=
{
<
Icon
aria
-
hidden
=
{
true
}
iconName
=
'ChevronRight'
/>
}
breakLabel
=
{
'
...
'
}
breakClassName
=
{
'
break
'
}
pageCount
=
{
pageCount
}
marginPagesDisplayed
=
{
2
}
pageRangeDisplayed
=
{
2
}
onPageChange
=
{
this
.
_onPageSelect
.
bind
(
this
)
}
containerClassName
=
{
itemsCount
===
0
?
'
pagination hidden
'
:
'
pagination
'
}
subContainerClassName
=
{
'
pages pagination
'
}
disableInitialCallback
=
{
false
}
activeClassName
=
{
'
active
'
}
/>
</
Stack
>
</
div
>
);
}
}
export
default
PaginationTable
;
src/webui/src/components/trial-detail/Para.tsx
View file @
88a225f8
import
*
as
d3
from
'
d3
'
;
import
*
as
d3
from
'
d3
'
;
import
{
Dropdown
,
IDropdownOption
,
Stack
}
from
'
@fluentui/react
'
;
import
{
Dropdown
,
IDropdownOption
,
Stack
,
DefaultButton
}
from
'
@fluentui/react
'
;
import
ParCoords
from
'
parcoord-es
'
;
import
ParCoords
from
'
parcoord-es
'
;
import
'
parcoord-es/dist/parcoords.css
'
;
import
'
parcoord-es/dist/parcoords.css
'
;
import
*
as
React
from
'
react
'
;
import
*
as
React
from
'
react
'
;
...
@@ -9,12 +9,16 @@ import { filterByStatus } from '../../static/function';
...
@@ -9,12 +9,16 @@ import { filterByStatus } from '../../static/function';
import
{
TableObj
,
SingleAxis
,
MultipleAxes
}
from
'
../../static/interface
'
;
import
{
TableObj
,
SingleAxis
,
MultipleAxes
}
from
'
../../static/interface
'
;
import
'
../../static/style/button.scss
'
;
import
'
../../static/style/button.scss
'
;
import
'
../../static/style/para.scss
'
;
import
'
../../static/style/para.scss
'
;
import
ChangeColumnComponent
from
'
../modals/ChangeColumnComponent
'
;
interface
ParaState
{
interface
ParaState
{
dimName
:
string
[];
dimName
:
string
[];
selectedPercent
:
string
;
selectedPercent
:
string
;
primaryMetricKey
:
string
;
primaryMetricKey
:
string
;
noChart
:
boolean
;
noChart
:
boolean
;
customizeColumnsDialogVisible
:
boolean
;
availableDimensions
:
string
[];
chosenDimensions
:
string
[];
}
}
interface
ParaProps
{
interface
ParaProps
{
...
@@ -45,7 +49,10 @@ class Para extends React.Component<ParaProps, ParaState> {
...
@@ -45,7 +49,10 @@ class Para extends React.Component<ParaProps, ParaState> {
dimName
:
[],
dimName
:
[],
primaryMetricKey
:
'
default
'
,
primaryMetricKey
:
'
default
'
,
selectedPercent
:
'
1
'
,
selectedPercent
:
'
1
'
,
noChart
:
true
noChart
:
true
,
customizeColumnsDialogVisible
:
false
,
availableDimensions
:
[],
chosenDimensions
:
[]
};
};
}
}
...
@@ -82,11 +89,24 @@ class Para extends React.Component<ParaProps, ParaState> {
...
@@ -82,11 +89,24 @@ class Para extends React.Component<ParaProps, ParaState> {
}
}
render
():
React
.
ReactNode
{
render
():
React
.
ReactNode
{
const
{
selectedPercent
,
noChart
}
=
this
.
state
;
const
{
selectedPercent
,
noChart
,
customizeColumnsDialogVisible
,
availableDimensions
,
chosenDimensions
}
=
this
.
state
;
return
(
return
(
<
div
className
=
'parameter'
>
<
div
className
=
'parameter'
>
<
Stack
horizontal
className
=
'para-filter'
horizontalAlign
=
'end'
>
<
Stack
horizontal
className
=
'para-filter'
horizontalAlign
=
'end'
>
<
DefaultButton
text
=
'Add/Remove axes'
onClick
=
{
():
void
=>
{
this
.
setState
({
customizeColumnsDialogVisible
:
true
});
}
}
styles
=
{
{
root
:
{
marginRight
:
10
}
}
}
/>
<
Dropdown
<
Dropdown
selectedKey
=
{
selectedPercent
}
selectedKey
=
{
selectedPercent
}
onChange
=
{
this
.
percentNum
}
onChange
=
{
this
.
percentNum
}
...
@@ -101,6 +121,21 @@ class Para extends React.Component<ParaProps, ParaState> {
...
@@ -101,6 +121,21 @@ class Para extends React.Component<ParaProps, ParaState> {
/>
/>
{
this
.
finalKeysDropdown
()
}
{
this
.
finalKeysDropdown
()
}
</
Stack
>
</
Stack
>
{
customizeColumnsDialogVisible
&&
availableDimensions
.
length
>
0
&&
(
<
ChangeColumnComponent
selectedColumns
=
{
chosenDimensions
}
allColumns
=
{
availableDimensions
.
map
(
dim
=>
({
key
:
dim
,
name
:
dim
}))
}
onSelectedChange
=
{
(
selected
:
string
[]):
void
=>
{
this
.
setState
({
chosenDimensions
:
selected
},
()
=>
{
this
.
renderParallelCoordinates
();
});
}
}
onHideDialog
=
{
():
void
=>
{
this
.
setState
({
customizeColumnsDialogVisible
:
false
});
}
}
minSelected
=
{
2
}
/>
)
}
<
div
className
=
'parcoords'
style
=
{
this
.
chartMulineStyle
}
ref
=
{
this
.
paraRef
}
/>
<
div
className
=
'parcoords'
style
=
{
this
.
chartMulineStyle
}
ref
=
{
this
.
paraRef
}
/>
{
noChart
&&
<
div
className
=
'nodata'
>
No data
</
div
>
}
{
noChart
&&
<
div
className
=
'nodata'
>
No data
</
div
>
}
</
div
>
</
div
>
...
@@ -143,13 +178,13 @@ class Para extends React.Component<ParaProps, ParaState> {
...
@@ -143,13 +178,13 @@ class Para extends React.Component<ParaProps, ParaState> {
private
renderParallelCoordinates
():
void
{
private
renderParallelCoordinates
():
void
{
const
{
searchSpace
}
=
this
.
props
;
const
{
searchSpace
}
=
this
.
props
;
const
percent
=
parseFloat
(
this
.
state
.
selectedPercent
);
const
percent
=
parseFloat
(
this
.
state
.
selectedPercent
);
const
{
primaryMetricKey
}
=
this
.
state
;
const
{
primaryMetricKey
,
chosenDimensions
}
=
this
.
state
;
const
inferredSearchSpace
=
TRIALS
.
inferredSearchSpace
(
searchSpace
);
const
inferredSearchSpace
=
TRIALS
.
inferredSearchSpace
(
searchSpace
);
const
inferredMetricSpace
=
TRIALS
.
inferredMetricSpace
();
const
inferredMetricSpace
=
TRIALS
.
inferredMetricSpace
();
let
convertedTrials
=
this
.
getTrialsAsObjectList
(
inferredSearchSpace
,
inferredMetricSpace
);
let
convertedTrials
=
this
.
getTrialsAsObjectList
(
inferredSearchSpace
,
inferredMetricSpace
);
const
dimensions
:
[
any
,
any
][]
=
[];
const
dimensions
:
[
string
,
any
][]
=
[];
let
colorDim
:
string
|
undefined
=
undefined
,
let
colorDim
:
string
|
undefined
=
undefined
,
colorScale
:
any
=
undefined
;
colorScale
:
any
=
undefined
;
// treat every axis as numeric to fit for brush
// treat every axis as numeric to fit for brush
...
@@ -213,7 +248,11 @@ class Para extends React.Component<ParaProps, ParaState> {
...
@@ -213,7 +248,11 @@ class Para extends React.Component<ParaProps, ParaState> {
}
}
this
.
pcs
this
.
pcs
.
data
(
convertedTrials
)
.
data
(
convertedTrials
)
.
dimensions
(
dimensions
.
reduce
((
obj
,
entry
)
=>
({
...
obj
,
[
entry
[
0
]]:
entry
[
1
]
}),
{}));
.
dimensions
(
dimensions
.
filter
(([
d
,
_
])
=>
chosenDimensions
.
length
===
0
||
chosenDimensions
.
includes
(
d
))
.
reduce
((
obj
,
entry
)
=>
({
...
obj
,
[
entry
[
0
]]:
entry
[
1
]
}),
{})
);
if
(
firstRun
)
{
if
(
firstRun
)
{
this
.
pcs
this
.
pcs
.
margin
(
this
.
innerChartMargins
)
.
margin
(
this
.
innerChartMargins
)
...
@@ -230,6 +269,12 @@ class Para extends React.Component<ParaProps, ParaState> {
...
@@ -230,6 +269,12 @@ class Para extends React.Component<ParaProps, ParaState> {
if
(
firstRun
)
{
if
(
firstRun
)
{
this
.
setState
({
noChart
:
false
});
this
.
setState
({
noChart
:
false
});
}
}
// set new available dims
this
.
setState
({
availableDimensions
:
dimensions
.
map
(
e
=>
e
[
0
]),
chosenDimensions
:
chosenDimensions
.
length
===
0
?
dimensions
.
map
(
e
=>
e
[
0
])
:
chosenDimensions
});
}
}
private
getTrialsAsObjectList
(
inferredSearchSpace
:
MultipleAxes
,
inferredMetricSpace
:
MultipleAxes
):
{}[]
{
private
getTrialsAsObjectList
(
inferredSearchSpace
:
MultipleAxes
,
inferredMetricSpace
:
MultipleAxes
):
{}[]
{
...
...
src/webui/src/components/trial-detail/TableList.tsx
View file @
88a225f8
import
React
,
{
lazy
}
from
'
react
'
;
import
axios
from
'
axios
'
;
import
ReactEcharts
from
'
echarts-for-react
'
;
import
{
import
{
Stack
,
DefaultButton
,
Dropdown
,
Dropdown
,
DetailsList
,
IDetailsListProps
,
DetailsListLayoutMode
,
PrimaryButton
,
Modal
,
IDropdownOption
,
IColumn
,
IColumn
,
Icon
,
IDropdownOption
,
PrimaryButton
,
Selection
,
Selection
,
SelectionMode
,
SelectionMode
,
IconButton
,
Stack
,
TooltipHost
,
StackItem
,
IStackTokens
TooltipHost
}
from
'
@fluentui/react
'
;
}
from
'
@fluentui/react
'
;
import
ReactPaginate
from
'
react-paginate
'
;
import
React
from
'
react
'
;
import
{
LineChart
,
blocked
,
copy
}
from
'
../buttons/Icon
'
;
import
{
MANAGER_IP
,
COLUMNPro
}
from
'
../../static/const
'
;
import
{
convertDuration
,
formatTimestamp
,
intermediateGraphOption
,
parseMetrics
}
from
'
../../static/function
'
;
import
{
EXPERIMENT
,
TRIALS
}
from
'
../../static/datamodel
'
;
import
{
EXPERIMENT
,
TRIALS
}
from
'
../../static/datamodel
'
;
import
{
TableRecord
,
TrialJobInfo
}
from
'
../../static/interface
'
;
import
{
convertDuration
,
formatTimestamp
}
from
'
../../static/function
'
;
const
Details
=
lazy
(()
=>
import
(
'
../overview/table/Details
'
));
import
{
TableObj
}
from
'
../../static/interface
'
;
const
ChangeColumnComponent
=
lazy
(()
=>
import
(
'
../modals/ChangeColumnComponent
'
));
const
Compare
=
lazy
(()
=>
import
(
'
../modals/Compare
'
));
const
KillJob
=
lazy
(()
=>
import
(
'
../modals/Killjob
'
));
const
Customize
=
lazy
(()
=>
import
(
'
../modals/CustomizedTrial
'
));
import
{
contentStyles
,
iconButtonStyles
}
from
'
../buttons/ModalTheme
'
;
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
'
;
import
'
../../static/style/table.scss
'
;
import
'
../../static/style/table.scss
'
;
import
'
../../static/style/button.scss
'
;
import
'
../../static/style/button.scss
'
;
import
'
../../static/style/logPath.scss
'
;
import
'
../../static/style/openRow.scss
'
;
import
'
../../static/style/openRow.scss
'
;
import
'
../../static/style/pagination.scss
'
;
import
'
../../static/style/pagination.scss
'
;
import
'
../../static/style/search.scss
'
;
import
'
../../static/style/table.scss
'
;
import
'
../../static/style/tableStatus.css
'
;
import
{
blocked
,
copy
,
LineChart
,
tableListIcon
}
from
'
../buttons/Icon
'
;
import
ChangeColumnComponent
from
'
../modals/ChangeColumnComponent
'
;
import
Compare
from
'
../modals/Compare
'
;
import
Customize
from
'
../modals/CustomizedTrial
'
;
import
KillJob
from
'
../modals/Killjob
'
;
import
ExpandableDetails
from
'
../public-child/ExpandableDetails
'
;
import
PaginationTable
from
'
../public-child/PaginationTable
'
;
import
{
Trial
}
from
'
../../static/model/trial
'
;
const
echarts
=
require
(
'
echarts/lib/echarts
'
);
const
echarts
=
require
(
'
echarts/lib/echarts
'
);
require
(
'
echarts/lib/chart/line
'
);
require
(
'
echarts/lib/chart/line
'
);
...
@@ -45,749 +43,518 @@ echarts.registerTheme('my_theme', {
...
@@ -45,749 +43,518 @@ echarts.registerTheme('my_theme', {
color
:
'
#3c8dbc
'
color
:
'
#3c8dbc
'
});
});
const
horizontalGapStackTokens
:
IStackTokens
=
{
type
SearchOptionType
=
'
id
'
|
'
trialnum
'
|
'
status
'
|
'
parameters
'
;
childrenGap
:
20
,
const
searchOptionLiterals
=
{
padding
:
10
id
:
'
ID
'
,
trialnum
:
'
Trial No.
'
,
status
:
'
Status
'
,
parameters
:
'
Parameters
'
};
};
interface
TableListProps
{
const
defaultDisplayedColumns
=
[
'
sequenceId
'
,
'
id
'
,
'
duration
'
,
'
status
'
,
'
latestAccuracy
'
];
pageSize
:
number
;
tableSource
:
Array
<
TableRecord
>
;
columnList
:
string
[];
// user select columnKeys
changeColumn
:
(
val
:
string
[])
=>
void
;
trialsUpdateBroadcast
:
number
;
}
interface
SortInfo
{
interface
SortInfo
{
field
:
string
;
field
:
string
;
isDescend
?:
boolean
;
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
{
if
(
columnKey
===
'
sequenceId
'
)
{
return
'
Trial No.
'
;
}
else
if
(
columnKey
===
'
id
'
)
{
return
'
ID
'
;
}
else
if
(
columnKey
===
'
intermediateCount
'
)
{
return
'
Intermediate results (#)
'
;
}
else
if
(
columnKey
.
startsWith
(
'
space/
'
))
{
return
columnKey
.
split
(
'
/
'
,
2
)[
1
]
+
'
(space)
'
;
}
else
if
(
columnKey
===
'
latestAccuracy
'
)
{
return
'
Default metric
'
;
// to align with the original design
}
else
if
(
columnKey
.
startsWith
(
'
metric/
'
))
{
return
columnKey
.
split
(
'
/
'
,
2
)[
1
]
+
'
(metric)
'
;
}
else
if
(
columnKey
.
startsWith
(
'
_
'
))
{
return
columnKey
;
}
else
{
// camel case to verbose form
const
withSpace
=
columnKey
.
replace
(
/
[
A-Z
]
/g
,
letter
=>
`
${
letter
.
toLowerCase
()}
`
);
return
withSpace
.
charAt
(
0
).
toUpperCase
()
+
withSpace
.
slice
(
1
);
}
}
interface
TableListProps
{
tableSource
:
TableObj
[];
trialsUpdateBroadcast
:
number
;
}
interface
TableListState
{
interface
TableListState
{
intermediateOption
:
object
;
displayedItems
:
any
[];
modalVisible
:
boolean
;
displayedColumns
:
string
[];
isObjFinal
:
boolean
;
columns
:
IColumn
[];
isShowColumn
:
boolean
;
searchType
:
SearchOptionType
;
selectRows
:
Array
<
any
>
;
searchText
:
string
;
isShowCompareModal
:
boolean
;
selectedRowIds
:
string
[];
selectedRowKeys
:
string
[]
|
number
[];
customizeColumnsDialogVisible
:
boolean
;
intermediateData
:
Array
<
object
>
;
// a trial's intermediate results (include dict)
compareDialogVisible
:
boolean
;
intermediateId
:
string
;
intermediateDialogTrial
:
TableObj
|
undefined
;
intermediateOtherKeys
:
string
[];
copiedTrialId
:
string
|
undefined
;
isShowCustomizedModal
:
boolean
;
sortInfo
:
SortInfo
;
copyTrialId
:
string
;
// user copy trial to submit a new customized trial
isCalloutVisible
:
boolean
;
// kill job button callout [kill or not kill job window]
intermediateKey
:
string
;
// intermeidate modal: which key is choosed.
isExpand
:
boolean
;
modalIntermediateWidth
:
number
;
modalIntermediateHeight
:
number
;
tableColumns
:
IColumn
[];
allColumnList
:
string
[];
tableSourceForSort
:
Array
<
TableRecord
>
;
sortMessage
:
SortInfo
;
offset
:
number
;
tablePerPage
:
Array
<
TableRecord
>
;
perPage
:
number
;
currentPage
:
number
;
pageCount
:
number
;
}
}
class
TableList
extends
React
.
Component
<
TableListProps
,
TableListState
>
{
class
TableList
extends
React
.
Component
<
TableListProps
,
TableListState
>
{
p
ublic
intervalTrialLog
=
10
;
p
rivate
_selection
:
Selection
;
p
ublic
t
rialId
!
:
string
;
p
rivate
_expandedT
rialId
s
:
Set
<
string
>
;
constructor
(
props
:
TableListProps
)
{
constructor
(
props
:
TableListProps
)
{
super
(
props
);
super
(
props
);
this
.
state
=
{
this
.
state
=
{
intermediateOption
:
{},
displayedItems
:
[],
modalVisible
:
false
,
displayedColumns
:
defaultDisplayedColumns
,
isObjFinal
:
false
,
columns
:
[],
isShowColumn
:
false
,
searchType
:
'
id
'
,
isShowCompareModal
:
false
,
searchText
:
''
,
selectRows
:
[],
customizeColumnsDialogVisible
:
false
,
selectedRowKeys
:
[],
// close selected trial message after modal closed
compareDialogVisible
:
false
,
intermediateData
:
[],
selectedRowIds
:
[],
intermediateId
:
''
,
intermediateDialogTrial
:
undefined
,
intermediateOtherKeys
:
[],
copiedTrialId
:
undefined
,
isShowCustomizedModal
:
false
,
sortInfo
:
{
field
:
''
,
isDescend
:
true
}
isCalloutVisible
:
false
,
copyTrialId
:
''
,
intermediateKey
:
'
default
'
,
isExpand
:
false
,
modalIntermediateWidth
:
window
.
innerWidth
,
modalIntermediateHeight
:
window
.
innerHeight
,
tableColumns
:
this
.
initTableColumnList
(
this
.
props
.
columnList
),
allColumnList
:
this
.
getAllColumnKeys
(),
sortMessage
:
{
field
:
''
,
isDescend
:
false
},
offset
:
0
,
tablePerPage
:
[],
perPage
:
20
,
currentPage
:
0
,
pageCount
:
0
,
tableSourceForSort
:
this
.
props
.
tableSource
};
};
}
// sort for table column
this
.
_selection
=
new
Selection
({
onColumnClick
=
(
ev
:
React
.
MouseEvent
<
HTMLElement
>
,
getColumn
:
IColumn
):
void
=>
{
onSelectionChanged
:
():
void
=>
{
const
{
tableColumns
}
=
this
.
state
;
this
.
setState
({
const
newColumns
:
IColumn
[]
=
tableColumns
.
slice
();
selectedRowIds
:
this
.
_selection
.
getSelection
().
map
(
s
=>
(
s
as
any
).
id
)
const
currColumn
:
IColumn
=
newColumns
.
filter
(
item
=>
getColumn
.
key
===
item
.
key
)[
0
];
});
newColumns
.
forEach
((
newCol
:
IColumn
)
=>
{
if
(
newCol
===
currColumn
)
{
currColumn
.
isSortedDescending
=
!
currColumn
.
isSortedDescending
;
currColumn
.
isSorted
=
true
;
}
else
{
newCol
.
isSorted
=
false
;
newCol
.
isSortedDescending
=
true
;
}
}
});
});
this
.
setState
(
this
.
_expandedTrialIds
=
new
Set
<
string
>
();
{
}
tableColumns
:
newColumns
,
sortMessage
:
{
field
:
getColumn
.
key
,
isDescend
:
currColumn
.
isSortedDescending
}
},
()
=>
{
this
.
updateData
();
}
);
};
AccuracyColumnConfig
:
any
=
{
name
:
'
Default metric
'
,
className
:
'
leftTitle
'
,
key
:
'
latestAccuracy
'
,
fieldName
:
'
latestAccuracy
'
,
minWidth
:
200
,
maxWidth
:
300
,
isResizable
:
true
,
data
:
'
number
'
,
onColumnClick
:
this
.
onColumnClick
,
onRender
:
(
item
):
React
.
ReactNode
=>
(
<
TooltipHost
content
=
{
item
.
formattedLatestAccuracy
}
>
<
div
className
=
'ellipsis'
>
{
item
.
formattedLatestAccuracy
}
</
div
>
</
TooltipHost
>
)
};
SequenceIdColumnConfig
:
any
=
{
name
:
'
Trial No.
'
,
key
:
'
sequenceId
'
,
fieldName
:
'
sequenceId
'
,
minWidth
:
80
,
maxWidth
:
240
,
className
:
'
tableHead
'
,
data
:
'
number
'
,
onColumnClick
:
this
.
onColumnClick
};
IdColumnConfig
:
any
=
{
name
:
'
ID
'
,
key
:
'
id
'
,
fieldName
:
'
id
'
,
minWidth
:
150
,
maxWidth
:
200
,
isResizable
:
true
,
data
:
'
string
'
,
onColumnClick
:
this
.
onColumnClick
,
className
:
'
tableHead leftTitle
'
};
StartTimeColumnConfig
:
any
=
{
name
:
'
Start time
'
,
key
:
'
startTime
'
,
fieldName
:
'
startTime
'
,
minWidth
:
150
,
maxWidth
:
400
,
isResizable
:
true
,
data
:
'
number
'
,
onColumnClick
:
this
.
onColumnClick
,
onRender
:
(
record
):
React
.
ReactNode
=>
<
span
>
{
formatTimestamp
(
record
.
startTime
)
}
</
span
>
};
EndTimeColumnConfig
:
any
=
{
name
:
'
End time
'
,
key
:
'
endTime
'
,
fieldName
:
'
endTime
'
,
minWidth
:
200
,
maxWidth
:
400
,
isResizable
:
true
,
data
:
'
number
'
,
onColumnClick
:
this
.
onColumnClick
,
onRender
:
(
record
):
React
.
ReactNode
=>
<
span
>
{
formatTimestamp
(
record
.
endTime
,
'
--
'
)
}
</
span
>
};
DurationColumnConfig
:
any
=
{
name
:
'
Duration
'
,
key
:
'
duration
'
,
fieldName
:
'
duration
'
,
minWidth
:
150
,
maxWidth
:
300
,
isResizable
:
true
,
data
:
'
number
'
,
onColumnClick
:
this
.
onColumnClick
,
onRender
:
(
record
):
React
.
ReactNode
=>
<
span
className
=
'durationsty'
>
{
convertDuration
(
record
.
duration
)
}
</
span
>
};
StatusColumnConfig
:
any
=
{
name
:
'
Status
'
,
key
:
'
status
'
,
fieldName
:
'
status
'
,
className
:
'
tableStatus
'
,
minWidth
:
150
,
maxWidth
:
250
,
isResizable
:
true
,
data
:
'
string
'
,
onColumnClick
:
this
.
onColumnClick
,
onRender
:
(
record
):
React
.
ReactNode
=>
<
span
className
=
{
`
${
record
.
status
}
commonStyle`
}
>
{
record
.
status
}
</
span
>
};
IntermediateCountColumnConfig
:
any
=
{
name
:
'
Intermediate result
'
,
dataIndex
:
'
intermediateCount
'
,
fieldName
:
'
intermediateCount
'
,
minWidth
:
150
,
maxWidth
:
200
,
isResizable
:
true
,
data
:
'
number
'
,
onColumnClick
:
this
.
onColumnClick
,
onRender
:
(
record
):
React
.
ReactNode
=>
<
span
>
{
`#
${
record
.
intermediateCount
}
`
}
</
span
>
};
showIntermediateModal
=
async
(
record
:
TrialJobInfo
,
event
:
React
.
SyntheticEvent
<
EventTarget
>
):
Promise
<
void
>
=>
{
/* Search related methods */
event
.
preventDefault
();
event
.
stopPropagation
();
// This functions as the filter for the final trials displayed in the current table
const
res
=
await
axios
.
get
(
`
${
MANAGER_IP
}
/metric-data/
${
record
.
id
}
`
);
private
_filterTrials
(
trials
:
TableObj
[]):
TableObj
[]
{
if
(
res
.
status
===
200
)
{
const
{
searchText
,
searchType
}
=
this
.
state
;
const
intermediateArr
:
number
[]
=
[];
// search a trial by Trial No. | Trial ID | Parameters | Status
// support intermediate result is dict because the last intermediate result is
let
searchFilter
=
(
_
:
TableObj
):
boolean
=>
true
;
// eslint-disable-line no-unused-vars
// final result in a succeed trial, it may be a dict.
if
(
searchText
.
trim
())
{
// get intermediate result dict keys array
if
(
searchType
===
'
id
'
)
{
const
{
intermediateKey
}
=
this
.
state
;
searchFilter
=
(
trial
):
boolean
=>
trial
.
id
.
toUpperCase
().
includes
(
searchText
.
toUpperCase
());
const
otherkeys
:
string
[]
=
[];
}
else
if
(
searchType
===
'
trialnum
'
)
{
const
metricDatas
=
res
.
data
;
searchFilter
=
(
trial
):
boolean
=>
trial
.
sequenceId
.
toString
()
===
searchText
;
if
(
metricDatas
.
length
!==
0
)
{
}
else
if
(
searchType
===
'
status
'
)
{
// just add type=number keys
searchFilter
=
(
trial
):
boolean
=>
trial
.
status
.
toUpperCase
().
includes
(
searchText
.
toUpperCase
());
const
intermediateMetrics
=
parseMetrics
(
metricDatas
[
0
].
data
);
}
else
if
(
searchType
===
'
parameters
'
)
{
for
(
const
key
in
intermediateMetrics
)
{
// TODO: support filters like `x: 2` (instead of `'x': 2`)
if
(
typeof
intermediateMetrics
[
key
]
===
'
number
'
)
{
searchFilter
=
(
trial
):
boolean
=>
JSON
.
stringify
(
trial
.
description
.
parameters
).
includes
(
searchText
);
otherkeys
.
push
(
key
);
}
}
}
}
// intermediateArr just store default val
metricDatas
.
map
(
item
=>
{
if
(
item
.
type
===
'
PERIODICAL
'
)
{
const
temp
=
parseMetrics
(
item
.
data
);
if
(
typeof
temp
===
'
object
'
)
{
intermediateArr
.
push
(
temp
[
intermediateKey
]);
}
else
{
intermediateArr
.
push
(
temp
);
}
}
});
const
intermediate
=
intermediateGraphOption
(
intermediateArr
,
record
.
id
);
this
.
setState
({
intermediateData
:
res
.
data
,
// store origin intermediate data for a trial
intermediateOption
:
intermediate
,
intermediateOtherKeys
:
otherkeys
,
intermediateId
:
record
.
id
});
}
}
this
.
setState
({
modalVisible
:
true
}
);
return
trials
.
filter
(
searchFilter
);
}
;
}
// intermediate button click -> intermediate graph for each trial
private
_updateSearchFilterType
(
_event
:
React
.
FormEvent
<
HTMLDivElement
>
,
item
:
IDropdownOption
|
undefined
):
void
{
// support intermediate is dict
selectOtherKeys
=
(
event
:
React
.
FormEvent
<
HTMLDivElement
>
,
item
?:
IDropdownOption
):
void
=>
{
if
(
item
!==
undefined
)
{
if
(
item
!==
undefined
)
{
const
value
=
item
.
text
;
const
value
=
item
.
key
.
toString
();
const
isShowDefault
:
boolean
=
value
===
'
default
'
?
true
:
false
;
if
(
searchOptionLiterals
.
hasOwnProperty
(
value
))
{
const
{
intermediateData
,
intermediateId
}
=
this
.
state
;
this
.
setState
({
searchType
:
value
as
SearchOptionType
},
this
.
_updateTableSource
);
const
intermediateArr
:
number
[]
=
[];
// just watch default key-val
if
(
isShowDefault
===
true
)
{
Object
.
keys
(
intermediateData
).
map
(
item
=>
{
if
(
intermediateData
[
item
].
type
===
'
PERIODICAL
'
)
{
const
temp
=
parseMetrics
(
intermediateData
[
item
].
data
);
if
(
typeof
temp
===
'
object
'
)
{
intermediateArr
.
push
(
temp
[
value
]);
}
else
{
intermediateArr
.
push
(
temp
);
}
}
});
}
else
{
Object
.
keys
(
intermediateData
).
map
(
item
=>
{
const
temp
=
parseMetrics
(
intermediateData
[
item
].
data
);
if
(
typeof
temp
===
'
object
'
)
{
intermediateArr
.
push
(
temp
[
value
]);
}
});
}
}
const
intermediate
=
intermediateGraphOption
(
intermediateArr
,
intermediateId
);
// re-render
this
.
setState
({
intermediateKey
:
value
,
intermediateOption
:
intermediate
});
}
}
};
}
hideIntermediateModal
=
():
void
=>
{
this
.
setState
({
modalVisible
:
false
});
};
hideShowColumnModal
=
():
void
=>
{
this
.
setState
(()
=>
({
isShowColumn
:
false
}));
};
// click add column btn, just show the modal of addcolumn
addColumn
=
():
void
=>
{
// show user select check button
this
.
setState
(()
=>
({
isShowColumn
:
true
}));
};
fillSelectedRowsTostate
=
(
selected
:
number
[]
|
string
[],
selectedRows
:
Array
<
TableRecord
>
):
void
=>
{
this
.
setState
({
selectRows
:
selectedRows
,
selectedRowKeys
:
selected
});
};
// open Compare-modal
private
_updateSearchText
(
ev
:
React
.
ChangeEvent
<
HTMLInputElement
>
):
void
{
compareBtn
=
():
void
=>
{
this
.
setState
({
searchText
:
ev
.
target
.
value
},
this
.
_updateTableSource
);
const
{
selectRows
}
=
this
.
state
;
}
if
(
selectRows
.
length
===
0
)
{
alert
(
'
Please select datas you want to compare!
'
);
}
else
{
this
.
setState
({
isShowCompareModal
:
true
});
}
};
// close Compare-modal
/* Table basic function related methods */
hideCompareModal
=
():
void
=>
{
// close modal. clear select rows data, clear selected track
this
.
setState
({
isShowCompareModal
:
false
,
selectedRowKeys
:
[],
selectRows
:
[]
});
};
// open customized trial modal
private
_onColumnClick
(
ev
:
React
.
MouseEvent
<
HTMLElement
>
,
column
:
IColumn
):
void
{
private
setCustomizedTrial
=
(
trialId
:
string
,
event
:
React
.
SyntheticEvent
<
EventTarget
>
):
void
=>
{
// handle the click events on table header (do sorting)
event
.
preventDefault
();
const
{
columns
}
=
this
.
state
;
event
.
stopPropagation
();
const
newColumns
:
IColumn
[]
=
columns
.
slice
();
this
.
setState
({
const
currColumn
:
IColumn
=
newColumns
.
filter
(
currCol
=>
column
.
key
===
currCol
.
key
)[
0
];
isShowCustomizedModal
:
true
,
const
isSortedDescending
=
!
currColumn
.
isSortedDescending
;
copyTrialId
:
trialId
this
.
setState
(
});
{
};
sortInfo
:
{
field
:
column
.
key
,
isDescend
:
isSortedDescending
}
},
this
.
_updateTableSource
);
}
private
closeCustomizedTrial
=
():
void
=>
{
private
_trialsToTableItems
(
trials
:
TableObj
[]):
any
[]
{
this
.
setState
({
// TODO: use search space and metrics space from TRIALS will cause update issues.
isShowCustomizedModal
:
false
,
const
searchSpace
=
TRIALS
.
inferredSearchSpace
(
EXPERIMENT
.
searchSpaceNew
);
copyTrialId
:
''
const
metricSpace
=
TRIALS
.
inferredMetricSpace
();
const
items
=
trials
.
map
(
trial
=>
{
const
ret
=
{
sequenceId
:
trial
.
sequenceId
,
id
:
trial
.
id
,
startTime
:
(
trial
as
Trial
).
info
.
startTime
,
// FIXME: why do we need info here?
endTime
:
(
trial
as
Trial
).
info
.
endTime
,
duration
:
trial
.
duration
,
status
:
trial
.
status
,
intermediateCount
:
trial
.
intermediates
.
length
,
_expandDetails
:
this
.
_expandedTrialIds
.
has
(
trial
.
id
)
// hidden field names should start with `_`
};
for
(
const
[
k
,
v
]
of
trial
.
parameters
(
searchSpace
))
{
ret
[
`space/
${
k
.
baseName
}
`
]
=
v
;
}
for
(
const
[
k
,
v
]
of
trial
.
metrics
(
metricSpace
))
{
ret
[
`metric/
${
k
.
baseName
}
`
]
=
v
;
}
ret
[
'
latestAccuracy
'
]
=
(
trial
as
Trial
).
latestAccuracy
;
ret
[
'
_formattedLatestAccuracy
'
]
=
(
trial
as
Trial
).
formatLatestAccuracy
();
return
ret
;
});
});
};
private
onWindowResize
=
():
void
=>
{
this
.
setState
(()
=>
({
modalIntermediateHeight
:
window
.
innerHeight
,
modalIntermediateWidth
:
window
.
innerWidth
}));
};
private
onRenderRow
:
IDetailsListProps
[
'
onRenderRow
'
]
=
props
=>
{
if
(
props
)
{
return
<
Details
detailsProps
=
{
props
}
/>;
}
return
null
;
};
private
getSelectedRows
=
new
Selection
({
const
{
sortInfo
}
=
this
.
state
;
onSelectionChanged
:
():
void
=>
{
if
(
sortInfo
.
field
!==
''
)
{
this
.
setState
(()
=>
({
selectRows
:
this
.
getSelectedRows
.
getSelection
()
}));
return
_copyAndSort
(
items
,
sortInfo
.
field
,
sortInfo
.
isDescend
);
}
}
else
{
});
return
items
;
// trial parameters & dict final keys & Trial No. Id ...
private
getAllColumnKeys
=
():
string
[]
=>
{
const
tableSource
:
Array
<
TableRecord
>
=
JSON
.
parse
(
JSON
.
stringify
(
this
.
props
.
tableSource
));
// parameter as table column
const
parameterStr
:
string
[]
=
[];
if
(
!
EXPERIMENT
.
isNestedExp
())
{
if
(
tableSource
.
length
>
0
)
{
const
trialMess
=
TRIALS
.
getTrial
(
tableSource
[
0
].
id
);
const
trial
=
trialMess
.
description
.
parameters
;
const
parameterColumn
:
string
[]
=
Object
.
keys
(
trial
);
parameterColumn
.
forEach
(
value
=>
{
parameterStr
.
push
(
`
${
value
}
(search space)`
);
});
}
}
}
// concat trial all final keys and remove dup "default" val, return list
}
const
finalKeysList
=
TRIALS
.
finalKeys
().
filter
(
item
=>
item
!==
'
default
'
);
return
COLUMNPro
.
concat
(
parameterStr
).
concat
(
finalKeysList
);
};
// get IColumn[]
private
_buildColumnsFromTableItems
(
tableItems
:
any
[]):
IColumn
[]
{
// when user click [Add Column] need to use the function
// extra column, for a icon to expand the trial details panel
private
initTableColumnList
=
(
columnList
:
string
[]):
IColumn
[]
=>
{
const
columns
:
IColumn
[]
=
[
// const { columnList } = this.props;
{
const
disabledAddCustomizedTrial
=
[
'
DONE
'
,
'
ERROR
'
,
'
STOPPED
'
].
includes
(
EXPERIMENT
.
status
);
key
:
'
_expand
'
,
const
showColumn
:
IColumn
[]
=
[];
name
:
''
,
for
(
const
item
of
columnList
)
{
onRender
:
(
item
,
index
):
any
=>
{
const
paraColumn
=
item
.
match
(
/
\(
search space
\)
$/
);
return
(
let
result
;
<
Icon
if
(
paraColumn
!==
null
)
{
aria
-
hidden
=
{
true
}
result
=
paraColumn
.
input
;
iconName
=
'ChevronRight'
styles
=
{
{
root
:
{
transition
:
'
all 0.2s
'
,
transform
:
`rotate(
${
item
.
_expandDetails
?
90
:
0
}
deg)`
}
}
}
onClick
=
{
(
event
):
void
=>
{
event
.
stopPropagation
();
const
newItem
:
any
=
{
...
item
,
_expandDetails
:
!
item
.
_expandDetails
};
if
(
newItem
.
_expandDetails
)
{
// preserve to be restored when refreshed
this
.
_expandedTrialIds
.
add
(
newItem
.
id
);
}
else
{
this
.
_expandedTrialIds
.
delete
(
newItem
.
id
);
}
const
newItems
=
[...
this
.
state
.
displayedItems
];
newItems
[
index
as
number
]
=
newItem
;
this
.
setState
({
displayedItems
:
newItems
});
}
}
onMouseDown
=
{
(
e
):
void
=>
{
e
.
stopPropagation
();
}
}
onMouseUp
=
{
(
e
):
void
=>
{
e
.
stopPropagation
();
}
}
/>
);
},
fieldName
:
'
expand
'
,
isResizable
:
false
,
minWidth
:
20
,
maxWidth
:
20
}
}
switch
(
item
)
{
];
case
'
Trial No.
'
:
// looking at the first row only for now
showColumn
.
push
(
this
.
SequenceIdColumnConfig
);
for
(
const
k
of
Object
.
keys
(
tableItems
[
0
]))
{
break
;
if
(
k
===
'
metric/default
'
)
{
case
'
ID
'
:
// FIXME: default metric is hacked as latestAccuracy currently
showColumn
.
push
(
this
.
IdColumnConfig
);
continue
;
break
;
case
'
Start time
'
:
showColumn
.
push
(
this
.
StartTimeColumnConfig
);
break
;
case
'
End time
'
:
showColumn
.
push
(
this
.
EndTimeColumnConfig
);
break
;
case
'
Duration
'
:
showColumn
.
push
(
this
.
DurationColumnConfig
);
break
;
case
'
Status
'
:
showColumn
.
push
(
this
.
StatusColumnConfig
);
break
;
case
'
Intermediate result
'
:
showColumn
.
push
(
this
.
IntermediateCountColumnConfig
);
break
;
case
'
Default
'
:
showColumn
.
push
(
this
.
AccuracyColumnConfig
);
break
;
case
'
Operation
'
:
showColumn
.
push
({
name
:
'
Operation
'
,
key
:
'
operation
'
,
fieldName
:
'
operation
'
,
minWidth
:
160
,
maxWidth
:
200
,
isResizable
:
true
,
className
:
'
detail-table
'
,
onRender
:
(
record
:
any
)
=>
{
const
trialStatus
=
record
.
status
;
const
flag
:
boolean
=
trialStatus
===
'
RUNNING
'
||
trialStatus
===
'
UNKNOWN
'
?
false
:
true
;
return
(
<
Stack
className
=
'detail-button'
horizontal
>
{
/* see intermediate result graph */
}
<
PrimaryButton
className
=
'detail-button-operation'
title
=
'Intermediate'
onClick
=
{
this
.
showIntermediateModal
.
bind
(
this
,
record
)
}
>
{
LineChart
}
</
PrimaryButton
>
{
/* kill job */
}
{
flag
?
(
<
PrimaryButton
className
=
'detail-button-operation'
disabled
=
{
true
}
title
=
'kill'
>
{
blocked
}
</
PrimaryButton
>
)
:
(
<
KillJob
trial
=
{
record
}
/>
)
}
{
/* Add a new trial-customized trial */
}
<
PrimaryButton
className
=
'detail-button-operation'
title
=
'Customized trial'
onClick
=
{
this
.
setCustomizedTrial
.
bind
(
this
,
record
.
id
)
}
disabled
=
{
disabledAddCustomizedTrial
}
>
{
copy
}
</
PrimaryButton
>
</
Stack
>
);
}
});
break
;
case
result
:
// remove SEARCH_SPACE title
// const realItem = item.replace(' (search space)', '');
showColumn
.
push
({
name
:
item
.
replace
(
'
(search space)
'
,
''
),
key
:
item
,
fieldName
:
item
,
minWidth
:
150
,
onRender
:
(
record
:
TableRecord
)
=>
{
const
eachTrial
=
TRIALS
.
getTrial
(
record
.
id
);
return
<
span
>
{
eachTrial
.
description
.
parameters
[
item
.
replace
(
'
(search space)
'
,
''
)]
}
</
span
>;
}
});
break
;
default
:
showColumn
.
push
({
name
:
item
,
key
:
item
,
fieldName
:
item
,
minWidth
:
100
,
onRender
:
(
record
:
TableRecord
)
=>
{
const
accDictionary
=
record
.
accDictionary
;
let
other
=
''
;
if
(
accDictionary
!==
undefined
)
{
other
=
accDictionary
[
item
].
toString
();
}
return
(
<
TooltipHost
content
=
{
other
}
>
<
div
className
=
'ellipsis'
>
{
other
}
</
div
>
</
TooltipHost
>
);
}
});
}
}
const
lengths
=
tableItems
.
map
(
item
=>
`
${
item
[
k
]}
`
.
length
);
const
avgLengths
=
lengths
.
reduce
((
a
,
b
)
=>
a
+
b
)
/
lengths
.
length
;
const
columnTitle
=
_inferColumnTitle
(
k
);
const
columnWidth
=
Math
.
max
(
columnTitle
.
length
,
avgLengths
);
// TODO: add blacklist
columns
.
push
({
name
:
columnTitle
,
key
:
k
,
fieldName
:
k
,
minWidth
:
columnWidth
*
13
,
maxWidth
:
columnWidth
*
18
,
isResizable
:
true
,
onColumnClick
:
this
.
_onColumnClick
.
bind
(
this
),
...(
k
===
'
status
'
&&
{
// color status
onRender
:
(
record
):
React
.
ReactNode
=>
(
<
span
className
=
{
`
${
record
.
status
}
commonStyle`
}
>
{
record
.
status
}
</
span
>
)
}),
...((
k
.
startsWith
(
'
metric/
'
)
||
k
.
startsWith
(
'
space/
'
))
&&
{
// show tooltip
onRender
:
(
record
):
React
.
ReactNode
=>
(
<
TooltipHost
content
=
{
record
[
k
]
}
>
<
div
className
=
'ellipsis'
>
{
record
[
k
]
}
</
div
>
</
TooltipHost
>
)
}),
...(
k
===
'
latestAccuracy
'
&&
{
// FIXME: this is ad-hoc
onRender
:
(
record
):
React
.
ReactNode
=>
(
<
TooltipHost
content
=
{
record
.
_formattedLatestAccuracy
}
>
<
div
className
=
'ellipsis'
>
{
record
.
_formattedLatestAccuracy
}
</
div
>
</
TooltipHost
>
)
}),
...([
'
startTime
'
,
'
endTime
'
].
includes
(
k
)
&&
{
onRender
:
(
record
):
React
.
ReactNode
=>
<
span
>
{
formatTimestamp
(
record
[
k
],
'
--
'
)
}
</
span
>
}),
...(
k
===
'
duration
'
&&
{
onRender
:
(
record
):
React
.
ReactNode
=>
(
<
span
className
=
'durationsty'
>
{
convertDuration
(
record
[
k
])
}
</
span
>
)
})
});
}
}
return
showColumn
;
// operations column
};
columns
.
push
({
name
:
'
Operation
'
,
componentDidMount
():
void
{
key
:
'
_operation
'
,
window
.
addEventListener
(
'
resize
'
,
this
.
onWindowResize
);
fieldName
:
'
operation
'
,
this
.
updateData
();
minWidth
:
160
,
}
maxWidth
:
200
,
isResizable
:
true
,
className
:
'
detail-table
'
,
onRender
:
this
.
_renderOperationColumn
.
bind
(
this
)
});
componentDidUpdate
(
prevProps
:
TableListProps
):
void
{
const
{
sortInfo
}
=
this
.
state
;
if
(
for
(
const
column
of
columns
)
{
this
.
props
.
columnList
!==
prevProps
.
columnList
||
if
(
column
.
key
===
sortInfo
.
field
)
{
this
.
props
.
tableSource
!==
prevProps
.
tableSource
||
column
.
isSorted
=
true
;
prevProps
.
trialsUpdateBroadcast
!==
this
.
props
.
trialsUpdateBroadcast
column
.
isSortedDescending
=
sortInfo
.
isDescend
;
)
{
}
else
{
const
{
columnList
}
=
this
.
props
;
column
.
isSorted
=
false
;
this
.
setState
(
column
.
isSortedDescending
=
true
;
{
}
tableColumns
:
this
.
initTableColumnList
(
columnList
),
allColumnList
:
this
.
getAllColumnKeys
()
},
()
=>
{
this
.
updateData
();
}
);
}
}
return
columns
;
}
}
// slice all table data into current page data
private
_updateTableSource
():
void
{
updateData
():
void
{
// call this method when trials or the computation of trial filter has changed
const
tableSource
:
Array
<
TableRecord
>
=
this
.
props
.
tableSource
;
const
items
=
this
.
_trialsToTableItems
(
this
.
_filterTrials
(
this
.
props
.
tableSource
));
const
{
offset
,
perPage
,
sortMessage
}
=
this
.
state
;
if
(
items
.
length
>
0
)
{
const
columns
=
this
.
_buildColumnsFromTableItems
(
items
);
if
(
sortMessage
.
field
!==
''
)
{
this
.
setState
({
tableSource
.
sort
(
function
(
a
,
b
):
any
{
displayedItems
:
items
,
if
(
columns
:
columns
a
[
sortMessage
.
field
]
===
undefined
||
});
Object
.
is
(
a
[
sortMessage
.
field
],
NaN
)
||
}
else
{
Object
.
is
(
a
[
sortMessage
.
field
],
Infinity
)
||
this
.
setState
({
Object
.
is
(
a
[
sortMessage
.
field
],
-
Infinity
)
||
displayedItems
:
[],
typeof
a
[
sortMessage
.
field
]
===
'
object
'
columns
:
[]
)
{
return
1
;
}
if
(
b
[
sortMessage
.
field
]
===
undefined
||
Object
.
is
(
b
[
sortMessage
.
field
],
NaN
)
||
Object
.
is
(
b
[
sortMessage
.
field
],
Infinity
)
||
Object
.
is
(
b
[
sortMessage
.
field
],
-
Infinity
)
||
typeof
b
[
sortMessage
.
field
]
===
'
object
'
)
{
return
-
1
;
}
return
(
sortMessage
.
isDescend
?
a
[
sortMessage
.
field
]
<
b
[
sortMessage
.
field
]
:
a
[
sortMessage
.
field
]
>
b
[
sortMessage
.
field
])
?
1
:
-
1
;
});
});
}
}
}
const
tableSlice
=
tableSource
.
slice
(
offset
,
offset
+
perPage
);
private
_updateDisplayedColumns
(
displayedColumns
:
string
[]):
void
{
const
curPageCount
=
Math
.
ceil
(
tableSource
.
length
/
perPage
);
this
.
setState
({
this
.
setState
({
tablePerPage
:
tableSlice
,
displayedColumns
:
displayedColumns
pageCount
:
curPageCount
});
});
}
}
// update data when click the page index of pagination
private
_renderOperationColumn
(
record
:
any
):
React
.
ReactNode
{
handlePageClick
=
(
evt
:
any
):
void
=>
{
const
runningTrial
:
boolean
=
[
'
RUNNING
'
,
'
UNKNOWN
'
].
includes
(
record
.
status
)
?
false
:
true
;
const
selectedPage
=
evt
.
selected
;
const
disabledAddCustomizedTrial
=
[
'
DONE
'
,
'
ERROR
'
,
'
STOPPED
'
].
includes
(
EXPERIMENT
.
status
);
const
offset
=
selectedPage
*
this
.
state
.
perPage
;
return
(
<
Stack
className
=
'detail-button'
horizontal
>
this
.
setState
(
<
PrimaryButton
{
className
=
'detail-button-operation'
currentPage
:
selectedPage
,
title
=
'Intermediate'
offset
:
offset
onClick
=
{
():
void
=>
{
},
const
{
tableSource
}
=
this
.
props
;
()
=>
{
const
trial
=
tableSource
.
find
(
trial
=>
trial
.
id
===
record
.
id
)
as
TableObj
;
this
.
updateData
();
this
.
setState
({
intermediateDialogTrial
:
trial
});
}
}
}
>
{
LineChart
}
</
PrimaryButton
>
{
runningTrial
?
(
<
PrimaryButton
className
=
'detail-button-operation'
disabled
=
{
true
}
title
=
'kill'
>
{
blocked
}
</
PrimaryButton
>
)
:
(
<
KillJob
trial
=
{
record
}
/>
)
}
<
PrimaryButton
className
=
'detail-button-operation'
title
=
'Customized trial'
onClick
=
{
():
void
=>
{
this
.
setState
({
copiedTrialId
:
record
.
id
});
}
}
disabled
=
{
disabledAddCustomizedTrial
}
>
{
copy
}
</
PrimaryButton
>
</
Stack
>
);
);
};
}
// update per page items when click the dropdown of pagination
updatePerPage
=
(
event
:
React
.
FormEvent
<
HTMLDivElement
>
,
item
:
IDropdownOption
|
undefined
):
void
=>
{
const
{
pageCount
}
=
this
.
state
;
if
(
item
!==
undefined
)
{
const
currentPerPage
=
item
.
key
===
'
all
'
?
this
.
props
.
tableSource
.
length
:
Number
(
item
.
key
);
const
currentPageCount
=
this
.
props
.
tableSource
.
length
<=
currentPerPage
?
1
:
pageCount
;
this
.
setState
(
componentDidUpdate
(
prevProps
:
TableListProps
):
void
{
{
if
(
this
.
props
.
tableSource
!==
prevProps
.
tableSource
)
{
perPage
:
currentPerPage
,
this
.
_updateTableSource
();
offset
:
0
,
currentPage
:
0
,
pageCount
:
currentPageCount
},
()
=>
{
this
.
updateData
();
}
);
}
}
};
}
componentDidMount
():
void
{
this
.
_updateTableSource
();
}
render
():
React
.
ReactNode
{
render
():
React
.
ReactNode
{
const
{
const
{
intermediateKey
,
displayedItems
,
modalIntermediateWidth
,
columns
,
modalIntermediateHeight
,
searchType
,
tableColumns
,
customizeColumnsDialogVisible
,
allColumnList
,
compareDialogVisible
,
isShowColumn
,
displayedColumns
,
modalVisible
,
selectedRowIds
,
selectRows
,
intermediateDialogTrial
,
isShowCompareModal
,
copiedTrialId
intermediateOtherKeys
,
isShowCustomizedModal
,
copyTrialId
,
intermediateOption
,
tablePerPage
}
=
this
.
state
;
}
=
this
.
state
;
const
{
columnList
}
=
this
.
props
;
const
perPageOptions
=
[
{
key
:
'
10
'
,
text
:
'
10 items per page
'
},
{
key
:
'
20
'
,
text
:
'
20 items per page
'
},
{
key
:
'
50
'
,
text
:
'
50 items per page
'
},
{
key
:
'
all
'
,
text
:
'
All items
'
}
];
return
(
return
(
<
Stack
>
<
div
id
=
'tableList'
>
<
div
id
=
'tableList'
>
<
Stack
horizontal
className
=
'panelTitle'
style
=
{
{
marginTop
:
10
}
}
>
<
DetailsList
<
span
style
=
{
{
marginRight
:
12
}
}
>
{
tableListIcon
}
</
span
>
columns
=
{
tableColumns
}
<
span
>
Trial jobs
</
span
>
items
=
{
tablePerPage
}
</
Stack
>
setKey
=
'set'
<
Stack
horizontal
className
=
'allList'
>
compact
=
{
true
}
<
StackItem
grow
=
{
50
}
>
onRenderRow
=
{
this
.
onRenderRow
}
<
DefaultButton
layoutMode
=
{
DetailsListLayoutMode
.
justified
}
text
=
'Compare'
selectionMode
=
{
SelectionMode
.
multiple
}
className
=
'allList-compare'
selection
=
{
this
.
getSelectedRows
}
onClick
=
{
():
void
=>
{
/>
this
.
setState
({
compareDialogVisible
:
true
});
}
}
<
Stack
disabled
=
{
selectedRowIds
.
length
===
0
}
horizontal
horizontalAlign
=
'end'
verticalAlign
=
'baseline'
styles
=
{
{
root
:
{
padding
:
10
}
}
}
tokens
=
{
horizontalGapStackTokens
}
>
<
Dropdown
selectedKey
=
{
this
.
state
.
perPage
===
this
.
props
.
tableSource
.
length
?
'
all
'
:
String
(
this
.
state
.
perPage
)
}
options
=
{
perPageOptions
}
onChange
=
{
this
.
updatePerPage
}
styles
=
{
{
dropdown
:
{
width
:
150
}
}
}
/>
<
ReactPaginate
previousLabel
=
{
'
<
'
}
nextLabel
=
{
'
>
'
}
breakLabel
=
{
'
...
'
}
breakClassName
=
{
'
break
'
}
pageCount
=
{
this
.
state
.
pageCount
}
marginPagesDisplayed
=
{
2
}
pageRangeDisplayed
=
{
2
}
onPageChange
=
{
this
.
handlePageClick
}
containerClassName
=
{
this
.
props
.
tableSource
.
length
==
0
?
'
pagination hidden
'
:
'
pagination
'
}
subContainerClassName
=
{
'
pages pagination
'
}
disableInitialCallback
=
{
false
}
activeClassName
=
{
'
active
'
}
forcePage
=
{
this
.
state
.
currentPage
}
/>
</
Stack
>
</
div
>
{
/* Intermediate Result Modal */
}
<
Modal
isOpen
=
{
modalVisible
}
onDismiss
=
{
this
.
hideIntermediateModal
}
containerClassName
=
{
contentStyles
.
container
}
>
<
div
className
=
{
contentStyles
.
header
}
>
<
span
>
Intermediate result
</
span
>
<
IconButton
styles
=
{
iconButtonStyles
}
iconProps
=
{
{
iconName
:
'
Cancel
'
}
}
ariaLabel
=
'Close popup modal'
onClick
=
{
this
.
hideIntermediateModal
as
any
}
/>
/>
</
div
>
</
StackItem
>
{
intermediateOtherKeys
.
length
>
1
?
(
<
StackItem
grow
=
{
50
}
>
<
Stack
horizontalAlign
=
'end'
className
=
'selectKeys'
>
<
Stack
horizontal
horizontalAlign
=
'end'
className
=
'allList'
>
<
DefaultButton
className
=
'allList-button-gap'
text
=
'Add/Remove columns'
onClick
=
{
():
void
=>
{
this
.
setState
({
customizeColumnsDialogVisible
:
true
});
}
}
/>
<
Dropdown
<
Dropdown
className
=
'select'
selectedKey
=
{
searchType
}
selectedKey
=
{
intermediateKey
}
options
=
{
Object
.
entries
(
searchOptionLiterals
).
map
(([
k
,
v
])
=>
({
options
=
{
intermediateOtherKeys
.
map
((
key
,
item
)
=>
{
key
:
k
,
return
{
text
:
v
key
:
key
,
}))
}
text
:
intermediateOtherKeys
[
item
]
onChange
=
{
this
.
_updateSearchFilterType
.
bind
(
this
)
}
};
styles
=
{
{
root
:
{
width
:
150
}
}
}
})
}
/>
onChange
=
{
this
.
selectOtherKeys
}
<
input
type
=
'text'
className
=
'allList-search-input'
placeholder
=
{
`Search by
${
[
'
id
'
,
'
trialnum
'
].
includes
(
searchType
)
?
searchOptionLiterals
[
searchType
]
:
searchType
}
`
}
onChange
=
{
this
.
_updateSearchText
.
bind
(
this
)
}
style
=
{
{
width
:
230
}
}
/>
/>
</
Stack
>
</
Stack
>
)
:
null
}
</
StackItem
>
<
div
className
=
'intermediate-graph'
>
</
Stack
>
<
ReactEcharts
{
columns
&&
displayedItems
&&
(
option
=
{
intermediateOption
}
<
PaginationTable
style
=
{
{
columns
=
{
columns
.
filter
(
width
:
0.5
*
modalIntermediateWidth
,
column
=>
height
:
0.7
*
modalIntermediateHeight
,
displayedColumns
.
includes
(
column
.
key
)
||
[
'
_expand
'
,
'
_operation
'
].
includes
(
column
.
key
)
maxHeight
:
534
,
)
}
padding
:
20
items
=
{
displayedItems
}
}
}
compact
=
{
true
}
theme
=
'my_theme'
selection
=
{
this
.
_selection
}
/>
selectionMode
=
{
SelectionMode
.
multiple
}
<
div
className
=
'xAxis'
>
#Intermediate result
</
div
>
selectionPreservedOnEmptyClick
=
{
true
}
</
div
>
onRenderRow
=
{
(
props
):
any
=>
{
</
Modal
>
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
{
/* Add Column Modal */
}
return
<
ExpandableDetails
detailsProps
=
{
props
!
}
isExpand
=
{
props
!
.
item
.
_expandDetails
}
/>;
{
isShowColumn
&&
(
}
}
/>
)
}
{
compareDialogVisible
&&
(
<
Compare
title
=
'Compare trials'
showDetails
=
{
true
}
trials
=
{
this
.
props
.
tableSource
.
filter
(
trial
=>
selectedRowIds
.
includes
(
trial
.
id
))
}
onHideDialog
=
{
():
void
=>
{
this
.
setState
({
compareDialogVisible
:
false
});
}
}
/>
)
}
{
intermediateDialogTrial
!==
undefined
&&
(
<
Compare
title
=
'Intermediate results'
showDetails
=
{
false
}
trials
=
{
[
intermediateDialogTrial
]
}
onHideDialog
=
{
():
void
=>
{
this
.
setState
({
intermediateDialogTrial
:
undefined
});
}
}
/>
)
}
{
customizeColumnsDialogVisible
&&
(
<
ChangeColumnComponent
<
ChangeColumnComponent
hideShowColumnDialog
=
{
this
.
hideShowColumnModal
}
selectedColumns
=
{
displayedColumns
}
isHideDialog
=
{
!
isShowColumn
}
allColumns
=
{
columns
showColumn
=
{
allColumnList
}
.
filter
(
column
=>
!
column
.
key
.
startsWith
(
'
_
'
))
selectedColumn
=
{
columnList
}
.
map
(
column
=>
({
key
:
column
.
key
,
name
:
column
.
name
}))
}
changeColumn
=
{
this
.
props
.
changeColumn
}
onSelectedChange
=
{
this
.
_updateDisplayedColumns
.
bind
(
this
)
}
onHideDialog
=
{
():
void
=>
{
this
.
setState
({
customizeColumnsDialogVisible
:
false
});
}
}
/>
/>
)
}
)
}
{
/* compare trials based message */
}
{
/* Clone a trial and customize a set of new parameters */
}
{
isShowCompareModal
&&
<
Compare
compareStacks
=
{
selectRows
}
cancelFunc
=
{
this
.
hideCompareModal
}
/>
}
{
/* visible is done inside because prompt is needed even when the dialog is closed */
}
{
/* clone trial parameters and could submit a customized trial */
}
<
Customize
<
Customize
visible
=
{
isShowCustomizedModal
}
visible
=
{
copiedTrialId
!==
undefined
}
copyTrialId
=
{
copyTrialId
}
copyTrialId
=
{
copiedTrialId
||
''
}
closeCustomizeModal
=
{
this
.
closeCustomizedTrial
}
closeCustomizeModal
=
{
():
void
=>
{
this
.
setState
({
copiedTrialId
:
undefined
});
}
}
/>
/>
</
Stack
>
</
div
>
);
);
}
}
}
}
...
...
src/webui/src/static/interface.ts
View file @
88a225f8
...
@@ -33,6 +33,7 @@ interface TableObj {
...
@@ -33,6 +33,7 @@ interface TableObj {
color
?:
string
;
color
?:
string
;
startTime
?:
number
;
startTime
?:
number
;
endTime
?:
number
;
endTime
?:
number
;
intermediates
:
(
MetricDataRecord
|
undefined
)[];
parameters
(
axes
:
MultipleAxes
):
Map
<
SingleAxis
,
any
>
;
parameters
(
axes
:
MultipleAxes
):
Map
<
SingleAxis
,
any
>
;
metrics
(
axes
:
MultipleAxes
):
Map
<
SingleAxis
,
any
>
;
metrics
(
axes
:
MultipleAxes
):
Map
<
SingleAxis
,
any
>
;
}
}
...
...
src/webui/src/static/model/trial.ts
View file @
88a225f8
...
@@ -60,7 +60,7 @@ function inferTrialParameters(
...
@@ -60,7 +60,7 @@ function inferTrialParameters(
class
Trial
implements
TableObj
{
class
Trial
implements
TableObj
{
private
metricsInitialized
:
boolean
=
false
;
private
metricsInitialized
:
boolean
=
false
;
private
infoField
:
TrialJobInfo
|
undefined
;
private
infoField
:
TrialJobInfo
|
undefined
;
p
rivate
intermediates
:
(
MetricDataRecord
|
undefined
)[]
=
[];
p
ublic
intermediates
:
(
MetricDataRecord
|
undefined
)[]
=
[];
public
final
:
MetricDataRecord
|
undefined
;
public
final
:
MetricDataRecord
|
undefined
;
private
finalAcc
:
number
|
undefined
;
private
finalAcc
:
number
|
undefined
;
...
@@ -224,24 +224,29 @@ class Trial implements TableObj {
...
@@ -224,24 +224,29 @@ class Trial implements TableObj {
}
}
public
parameters
(
axes
:
MultipleAxes
):
Map
<
SingleAxis
,
any
>
{
public
parameters
(
axes
:
MultipleAxes
):
Map
<
SingleAxis
,
any
>
{
const
ret
=
new
Map
<
SingleAxis
,
any
>
(
Array
.
from
(
axes
.
axes
.
values
()).
map
(
k
=>
[
k
,
null
]));
if
(
this
.
info
===
undefined
||
this
.
info
.
hyperParameters
===
undefined
)
{
if
(
this
.
info
===
undefined
||
this
.
info
.
hyperParameters
===
undefined
)
{
throw
new
Map
()
;
throw
ret
;
}
else
{
}
else
{
const
tempHyper
=
this
.
info
.
hyperParameters
;
const
tempHyper
=
this
.
info
.
hyperParameters
;
let
params
=
JSON
.
parse
(
tempHyper
[
tempHyper
.
length
-
1
]).
parameters
;
let
params
=
JSON
.
parse
(
tempHyper
[
tempHyper
.
length
-
1
]).
parameters
;
if
(
typeof
params
===
'
string
'
)
{
if
(
typeof
params
===
'
string
'
)
{
params
=
JSON
.
parse
(
params
);
params
=
JSON
.
parse
(
params
);
}
}
const
[
result
,
unexpectedEntries
]
=
inferTrialParameters
(
params
,
axes
);
const
[
updated
,
unexpectedEntries
]
=
inferTrialParameters
(
params
,
axes
);
if
(
unexpectedEntries
.
size
)
{
if
(
unexpectedEntries
.
size
)
{
throw
unexpectedEntries
;
throw
unexpectedEntries
;
}
}
return
result
;
for
(
const
[
k
,
v
]
of
updated
)
{
ret
.
set
(
k
,
v
);
}
return
ret
;
}
}
}
}
public
metrics
(
space
:
MultipleAxes
):
Map
<
SingleAxis
,
any
>
{
public
metrics
(
space
:
MultipleAxes
):
Map
<
SingleAxis
,
any
>
{
const
ret
=
new
Map
<
SingleAxis
,
any
>
();
// set default value: null
const
ret
=
new
Map
<
SingleAxis
,
any
>
(
Array
.
from
(
space
.
axes
.
values
()).
map
(
k
=>
[
k
,
null
]));
const
unexpectedEntries
=
new
Map
<
string
,
any
>
();
const
unexpectedEntries
=
new
Map
<
string
,
any
>
();
if
(
this
.
acc
===
undefined
)
{
if
(
this
.
acc
===
undefined
)
{
return
ret
;
return
ret
;
...
...
src/webui/src/static/style/table.scss
View file @
88a225f8
...
@@ -58,7 +58,7 @@
...
@@ -58,7 +58,7 @@
}
}
.detail-table
{
.detail-table
{
padding
:
5px
0
0
0
;
padding
-top
:
5px
;
}
}
.columns-height
{
.columns-height
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment