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
Show 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 @@
"@typescript-eslint/no-inferrable-types": 0,
"@typescript-eslint/no-use-before-define": [2, "nofunc"],
"@typescript-eslint/no-var-requires": 0,
"@typescript-eslint/no-unused-vars": [2, { "argsIgnorePattern": "^_" }],
"arrow-parens": [2, "as-needed"],
"no-inner-declarations": 0,
"no-empty": 2,
...
...
src/webui/src/components/TrialsDetail.tsx
View file @
88a225f8
import
*
as
React
from
'
react
'
;
import
{
Stack
,
StackItem
,
Pivot
,
PivotItem
,
Dropdown
,
IDropdownOption
,
DefaultButton
}
from
'
@fluentui/react
'
;
import
{
Stack
,
Pivot
,
PivotItem
}
from
'
@fluentui/react
'
;
import
{
EXPERIMENT
,
TRIALS
}
from
'
../static/datamodel
'
;
import
{
Trial
}
from
'
../static/model/trial
'
;
import
{
AppContext
}
from
'
../App
'
;
import
{
Title
}
from
'
./overview/Title
'
;
import
{
TitleContext
}
from
'
./overview/TitleContext
'
;
import
DefaultPoint
from
'
./trial-detail/DefaultMetricPoint
'
;
import
Duration
from
'
./trial-detail/Duration
'
;
import
Para
from
'
./trial-detail/Para
'
;
...
...
@@ -13,18 +10,8 @@ import TableList from './trial-detail/TableList';
import
'
../static/style/trialsDetail.scss
'
;
import
'
../static/style/search.scss
'
;
const
searchOptions
=
[
{
key
:
'
id
'
,
text
:
'
Id
'
},
{
key
:
'
Trial No.
'
,
text
:
'
Trial No.
'
},
{
key
:
'
status
'
,
text
:
'
Status
'
},
{
key
:
'
parameters
'
,
text
:
'
Parameters
'
}
];
interface
TrialDetailState
{
tablePageSize
:
number
;
// table components val
whichChart
:
string
;
searchType
:
string
;
searchFilter
:
(
trial
:
Trial
)
=>
boolean
;
}
class
TrialsDetail
extends
React
.
Component
<
{},
TrialDetailState
>
{
...
...
@@ -39,71 +26,22 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> {
constructor
(
props
)
{
super
(
props
);
this
.
state
=
{
tablePageSize
:
20
,
whichChart
:
'
Default metric
'
,
searchType
:
'
id
'
,
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/explicit-function-return-type
searchFilter
:
trial
=>
true
whichChart
:
'
Default metric
'
};
}
// search a trial by trial No. | trial id | Parameters | Status
searchTrial
=
(
event
:
React
.
ChangeEvent
<
HTMLInputElement
>
):
void
=>
{
const
targetValue
=
event
.
target
.
value
;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let
filter
=
(
trial
:
Trial
):
boolean
=>
true
;
if
(
!
targetValue
.
trim
())
{
this
.
setState
({
searchFilter
:
filter
});
return
;
}
switch
(
this
.
state
.
searchType
)
{
case
'
id
'
:
filter
=
(
trial
):
boolean
=>
trial
.
info
.
id
.
toUpperCase
().
includes
(
targetValue
.
toUpperCase
());
break
;
case
'
Trial No.
'
:
filter
=
(
trial
):
boolean
=>
trial
.
info
.
sequenceId
.
toString
()
===
targetValue
;
break
;
case
'
status
'
:
filter
=
(
trial
):
boolean
=>
trial
.
info
.
status
.
toUpperCase
().
includes
(
targetValue
.
toUpperCase
());
break
;
case
'
parameters
'
:
// TODO: support filters like `x: 2` (instead of `"x": 2`)
filter
=
(
trial
):
boolean
=>
JSON
.
stringify
(
trial
.
info
.
hyperParameters
,
null
,
4
).
includes
(
targetValue
);
break
;
default
:
alert
(
`Unexpected search filter
${
this
.
state
.
searchType
}
`
);
}
this
.
setState
({
searchFilter
:
filter
});
};
handleTablePageSizeSelect
=
(
event
:
React
.
FormEvent
<
HTMLDivElement
>
,
item
:
IDropdownOption
|
undefined
):
void
=>
{
if
(
item
!==
undefined
)
{
this
.
setState
({
tablePageSize
:
item
.
text
===
'
all
'
?
-
1
:
parseInt
(
item
.
text
,
10
)
});
}
};
handleWhichTabs
=
(
item
:
any
):
void
=>
{
this
.
setState
({
whichChart
:
item
.
props
.
headerText
});
};
updateSearchFilterType
=
(
event
:
React
.
FormEvent
<
HTMLDivElement
>
,
item
:
IDropdownOption
|
undefined
):
void
=>
{
// clear input value and re-render table
if
(
item
!==
undefined
)
{
if
(
this
.
searchInput
!==
null
)
{
this
.
searchInput
.
value
=
''
;
}
this
.
setState
(()
=>
({
searchType
:
item
.
key
.
toString
()
}));
}
};
render
():
React
.
ReactNode
{
const
{
tablePageSize
,
whichChart
,
searchType
}
=
this
.
state
;
const
source
=
TRIALS
.
filter
(
this
.
state
.
searchFilter
);
const
trialIds
=
TRIALS
.
filter
(
this
.
state
.
searchFilter
).
map
(
trial
=>
trial
.
id
);
const
{
whichChart
}
=
this
.
state
;
const
source
=
TRIALS
.
toArray
(
);
const
trialIds
=
TRIALS
.
toArray
(
).
map
(
trial
=>
trial
.
id
);
return
(
<
AppContext
.
Consumer
>
{
(
value
):
React
.
ReactNode
=>
(
{
(
_
value
):
React
.
ReactNode
=>
(
<
React
.
Fragment
>
<
div
className
=
'trial'
id
=
'tabsty'
>
<
Pivot
...
...
@@ -144,61 +82,10 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> {
</
Pivot
>
</
div
>
{
/* trial table list */
}
<
div
className
=
'bulletedList'
style
=
{
{
marginTop
:
18
}
}
>
<
Stack
className
=
'title'
>
<
TitleContext
.
Provider
value
=
{
{
text
:
'
Trial jobs
'
,
icon
:
'
BulletedList
'
}
}
>
<
Title
/>
</
TitleContext
.
Provider
>
</
Stack
>
<
Stack
horizontal
className
=
'allList'
>
<
StackItem
grow
=
{
50
}
>
<
DefaultButton
text
=
'Compare'
className
=
'allList-compare'
// use child-component tableList's function, the function is in child-component.
onClick
=
{
():
void
=>
{
if
(
this
.
tableList
)
{
this
.
tableList
.
compareBtn
();
}
}
}
/>
</
StackItem
>
<
StackItem
grow
=
{
50
}
>
<
Stack
horizontal
horizontalAlign
=
'end'
className
=
'allList'
>
<
DefaultButton
className
=
'allList-button-gap'
text
=
'Add column'
onClick
=
{
():
void
=>
{
if
(
this
.
tableList
)
{
this
.
tableList
.
addColumn
();
}
}
}
/>
<
Dropdown
selectedKey
=
{
searchType
}
options
=
{
searchOptions
}
onChange
=
{
this
.
updateSearchFilterType
}
styles
=
{
{
root
:
{
width
:
150
}
}
}
/>
<
input
type
=
'text'
className
=
'allList-search-input'
placeholder
=
{
`Search by
${
this
.
state
.
searchType
}
`
}
onChange
=
{
this
.
searchTrial
}
style
=
{
{
width
:
230
}
}
ref
=
{
(
text
):
any
=>
(
this
.
searchInput
=
text
)
}
/>
</
Stack
>
</
StackItem
>
</
Stack
>
<
div
style
=
{
{
backgroundColor
:
'
#fff
'
}
}
>
<
TableList
pageSize
=
{
tablePageSize
}
tableSource
=
{
source
.
map
(
trial
=>
trial
.
tableRecord
)
}
columnList
=
{
value
.
columnList
}
changeColumn
=
{
value
.
changeColumn
}
tableSource
=
{
source
}
trialsUpdateBroadcast
=
{
this
.
context
.
trialsUpdateBroadcast
}
// TODO: change any to specific type
ref
=
{
(
tabList
):
any
=>
(
this
.
tableList
=
tabList
)
}
/>
</
div
>
</
React
.
Fragment
>
...
...
src/webui/src/components/modals/ChangeColumnComponent.tsx
View file @
88a225f8
import
*
as
React
from
'
react
'
;
import
{
Dialog
,
DialogType
,
DialogFooter
,
Checkbox
,
PrimaryButton
,
DefaultButton
}
from
'
@fluentui/react
'
;
import
{
OPERATION
}
from
'
../../static/const
'
;
interface
ChangeColumnState
{
userSelectColumnList
:
string
[];
originSelectColumnList
:
string
[];
// buffer, not saved yet
currentSelected
:
string
[];
}
interface
ChangeColumnProps
{
isHideDialog
:
boolean
;
showColumn
:
string
[];
// all column List
selectedColumn
:
string
[];
// user selected column list
changeColumn
:
(
val
:
string
[])
=>
void
;
hideShowColumnDialog
:
()
=>
void
;
allColumns
:
SimpleColumn
[];
// all column List
selectedColumns
:
string
[];
// user selected column list
onSelectedChange
:
(
val
:
string
[])
=>
void
;
onHideDialog
:
()
=>
void
;
minSelected
?:
number
;
}
interface
SimpleColumn
{
key
:
string
;
// key for management
name
:
string
;
// name to display
}
interface
CheckBoxItems
{
...
...
@@ -20,12 +24,12 @@ interface CheckBoxItems {
checked
:
boolean
;
onChange
:
()
=>
void
;
}
class
ChangeColumnComponent
extends
React
.
Component
<
ChangeColumnProps
,
ChangeColumnState
>
{
constructor
(
props
:
ChangeColumnProps
)
{
super
(
props
);
this
.
state
=
{
userSelectColumnList
:
this
.
props
.
selectedColumn
,
originSelectColumnList
:
this
.
props
.
selectedColumn
currentSelected
:
this
.
props
.
selectedColumns
};
}
...
...
@@ -38,97 +42,50 @@ class ChangeColumnComponent extends React.Component<ChangeColumnProps, ChangeCol
label
:
string
,
val
?:
boolean
):
void
=>
{
const
source
:
string
[]
=
JSON
.
parse
(
JSON
.
stringify
(
this
.
state
.
userSelectColumnList
))
;
const
source
:
string
[]
=
[...
this
.
state
.
currentSelected
]
;
if
(
val
===
true
)
{
if
(
!
source
.
includes
(
label
))
{
source
.
push
(
label
);
this
.
setState
(
()
=>
({
userSelectColumnList
:
source
})
)
;
this
.
setState
(
{
currentSelected
:
source
});
}
}
else
{
if
(
source
.
includes
(
label
))
{
// remove from source
const
result
=
source
.
filter
(
item
=>
item
!==
label
);
this
.
setState
(()
=>
({
userSelectColumnList
:
result
}));
}
this
.
setState
({
currentSelected
:
result
});
}
};
saveUserSelectColumn
=
():
void
=>
{
const
{
userSelectColumnList
}
=
this
.
state
;
const
{
showColumn
}
=
this
.
props
;
// sort by Trial No. | ID | Duration | Start Time | End Time | ...
const
sortColumn
:
string
[]
=
[];
/**
*
* TODO: use this function to refactor sort column
* search space might orderless
showColumn.map(item => {
userSelectColumnList.map(key => {
if (item === key || key.includes('search space')) {
if (!sortColumn.includes(key)) {
sortColumn.push(key);
}
}
});
});
*/
// push ![Operation] ![search space] column
showColumn
.
map
(
item
=>
{
userSelectColumnList
.
map
(
key
=>
{
if
(
item
===
key
&&
item
!==
OPERATION
)
{
sortColumn
.
push
(
key
);
}
});
});
// push search space key
userSelectColumnList
.
map
(
index
=>
{
if
(
index
.
includes
(
'
search space
'
))
{
if
(
!
sortColumn
.
includes
(
index
))
{
sortColumn
.
push
(
index
);
}
}
});
// push Operation
if
(
userSelectColumnList
.
includes
(
OPERATION
))
{
sortColumn
.
push
(
OPERATION
);
}
this
.
props
.
changeColumn
(
sortColumn
);
this
.
hideDialog
();
// hide dialog
};
hideDialog
=
():
void
=>
{
this
.
props
.
hideShowColumnDialog
();
const
{
currentSelected
}
=
this
.
state
;
const
{
allColumns
,
onSelectedChange
}
=
this
.
props
;
const
selectedColumns
=
allColumns
.
map
(
column
=>
column
.
key
).
filter
(
key
=>
currentSelected
.
includes
(
key
));
onSelectedChange
(
selectedColumns
);
this
.
hideDialog
();
};
// user exit dialog
cancelOption
=
():
void
=>
{
// reset select column
const
{
originSelectColumnList
}
=
this
.
state
;
this
.
setState
({
userSelectColumnList
:
originSelectColumnList
},
()
=>
{
this
.
setState
({
currentSelected
:
this
.
props
.
selectedColumns
},
()
=>
{
this
.
hideDialog
();
});
};
private
hideDialog
=
():
void
=>
{
this
.
props
.
onHideDialog
();
};
render
():
React
.
ReactNode
{
const
{
showColumn
,
isHideDialog
}
=
this
.
props
;
const
{
userSelectColumnList
}
=
this
.
state
;
const
renderOptions
:
Array
<
CheckBoxItems
>
=
[];
showColumn
.
map
(
item
=>
{
if
(
userSelectColumnList
.
includes
(
item
))
{
// selected column name
renderOptions
.
push
({
label
:
item
,
checked
:
true
,
onChange
:
this
.
makeChangeHandler
(
item
)
});
}
else
{
renderOptions
.
push
({
label
:
item
,
checked
:
false
,
onChange
:
this
.
makeChangeHandler
(
item
)
});
}
});
const
{
allColumns
,
minSelected
}
=
this
.
props
;
const
{
currentSelected
}
=
this
.
state
;
return
(
<
div
>
<
Dialog
hidden
=
{
isHideDialog
}
// required field!
hidden
=
{
false
}
dialogContentProps
=
{
{
type
:
DialogType
.
largeHeader
,
title
:
'
C
hange tabl
e column
'
,
subText
:
'
You can chose which columns you w
ant
to see
in the table
.
'
title
:
'
C
ustomiz
e column
s
'
,
subText
:
'
You can cho
o
se which columns you w
ish
to see.
'
}
}
modalProps
=
{
{
isBlocking
:
false
,
...
...
@@ -136,12 +93,22 @@ class ChangeColumnComponent extends React.Component<ChangeColumnProps, ChangeCol
}
}
>
<
div
className
=
'columns-height'
>
{
renderOptions
.
map
(
item
=>
{
return
<
Checkbox
key
=
{
item
.
label
}
{
...
item
}
styles
=
{
{
root
:
{
marginBottom
:
8
}
}
}
/>;
})
}
{
allColumns
.
map
(
item
=>
(
<
Checkbox
key
=
{
item
.
key
}
label
=
{
item
.
name
}
checked
=
{
currentSelected
.
includes
(
item
.
key
)
}
onChange
=
{
this
.
makeChangeHandler
(
item
.
key
)
}
styles
=
{
{
root
:
{
marginBottom
:
8
}
}
}
/>
))
}
</
div
>
<
DialogFooter
>
<
PrimaryButton
text
=
'Save'
onClick
=
{
this
.
saveUserSelectColumn
}
/>
<
PrimaryButton
text
=
'Save'
onClick
=
{
this
.
saveUserSelectColumn
}
disabled
=
{
currentSelected
.
length
<
(
minSelected
===
undefined
?
1
:
minSelected
)
}
/>
<
DefaultButton
text
=
'Cancel'
onClick
=
{
this
.
cancelOption
}
/>
</
DialogFooter
>
</
Dialog
>
...
...
src/webui/src/components/modals/Compare.tsx
View file @
88a225f8
import
*
as
React
from
'
react
'
;
import
{
renderToString
}
from
'
react-dom/server
'
;
import
{
Stack
,
Modal
,
IconButton
,
IDragOptions
,
ContextualMenu
}
from
'
@fluentui/react
'
;
import
ReactEcharts
from
'
echarts-for-react
'
;
import
IntermediateVal
from
'
../public-child/IntermediateVal
'
;
import
{
TRIALS
}
from
'
../../static/datamodel
'
;
import
{
TableRecord
,
Intermedia
,
TooltipForIntermediate
}
from
'
../../static/interface
'
;
import
{
TooltipForIntermediate
,
TableObj
,
SingleAxis
}
from
'
../../static/interface
'
;
import
{
contentStyles
,
iconButtonStyles
}
from
'
../buttons/ModalTheme
'
;
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
=
{
moveMenuItemText
:
'
Move
'
,
...
...
@@ -13,79 +18,81 @@ const dragOptions: IDragOptions = {
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
{
compareStacks
:
Array
<
TableRecord
>
;
cancelFunc
:
()
=>
void
;
trials
:
TableObj
[];
title
:
string
;
showDetails
:
boolean
;
onHideDialog
:
()
=>
void
;
}
class
Compare
extends
React
.
Component
<
CompareProps
,
{}
>
{
public
_isCompareMount
!
:
boolean
;
constructor
(
props
:
CompareProps
)
{
super
(
props
);
}
intermediate
=
():
React
.
ReactNode
=>
{
const
{
compareStacks
}
=
this
.
props
;
const
trialIntermediate
:
Array
<
Intermedia
>
=
[];
const
idsList
:
string
[]
=
[];
compareStacks
.
forEach
(
element
=>
{
const
trial
=
TRIALS
.
getTrial
(
element
.
id
);
trialIntermediate
.
push
({
name
:
element
.
id
,
data
:
trial
.
description
.
intermediate
,
type
:
'
line
'
,
hyperPara
:
trial
.
description
.
parameters
});
idsList
.
push
(
element
.
id
);
});
// find max intermediate number
trialIntermediate
.
sort
((
a
,
b
)
=>
{
return
b
.
data
.
length
-
a
.
data
.
length
;
});
const
legend
:
string
[]
=
[];
// max length
const
length
=
trialIntermediate
[
0
]
!==
undefined
?
trialIntermediate
[
0
].
data
.
length
:
0
;
const
xAxis
:
number
[]
=
[];
trialIntermediate
.
forEach
(
element
=>
{
legend
.
push
(
element
.
name
);
});
for
(
let
i
=
1
;
i
<=
length
;
i
++
)
{
xAxis
.
push
(
i
);
private
_generateTooltipSummary
(
row
:
Item
,
metricKey
:
string
):
string
{
return
renderToString
(
<
div
className
=
'tooldetailAccuracy'
>
<
div
>
Trial ID:
{
row
.
id
}
</
div
>
<
div
>
Default metric:
{
row
.
metrics
.
get
(
metricKey
)
||
'
N/A
'
}
</
div
>
</
div
>
);
}
private
_intermediates
(
items
:
Item
[],
metricKey
:
string
):
React
.
ReactNode
{
// Precondition: make sure `items` is not empty
const
xAxisMax
=
Math
.
max
(...
items
.
map
(
item
=>
item
.
intermediates
.
length
));
const
xAxis
=
Array
(
xAxisMax
)
.
fill
(
0
)
.
map
((
_
,
i
)
=>
i
+
1
);
// [1, 2, 3, ..., xAxisMax]
const
dataForEchart
=
items
.
map
(
item
=>
({
name
:
item
.
id
,
data
:
item
.
intermediates
,
type
:
'
line
'
}));
const
legend
=
dataForEchart
.
map
(
item
=>
item
.
name
);
const
option
=
{
tooltip
:
{
trigger
:
'
item
'
,
enterable
:
true
,
position
:
function
(
point
:
number
[],
data
:
TooltipForIntermediate
):
number
[]
{
position
:
(
point
:
number
[],
data
:
TooltipForIntermediate
):
[
number
,
number
]
=>
{
if
(
data
.
dataIndex
<
length
/
2
)
{
return
[
point
[
0
],
80
];
}
else
{
return
[
point
[
0
]
-
300
,
80
];
}
},
formatter
:
function
(
data
:
TooltipForIntermediate
):
React
.
ReactNode
{
const
trialId
=
data
.
seriesName
;
let
obj
=
{};
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>
'
);
formatter
:
(
data
:
TooltipForIntermediate
):
string
=>
{
const
item
=
items
.
find
(
k
=>
k
.
id
===
data
.
seriesName
)
as
Item
;
return
this
.
_generateTooltipSummary
(
item
,
metricKey
);
}
},
grid
:
{
...
...
@@ -96,12 +103,11 @@ class Compare extends React.Component<CompareProps, {}> {
legend
:
{
type
:
'
scroll
'
,
right
:
40
,
left
:
idsList
.
length
>
6
?
80
:
null
,
data
:
idsList
left
:
legend
.
length
>
6
?
80
:
null
,
data
:
legend
},
xAxis
:
{
type
:
'
category
'
,
// name: '# Intermediate',
boundaryGap
:
false
,
data
:
xAxis
},
...
...
@@ -110,7 +116,7 @@ class Compare extends React.Component<CompareProps, {}> {
name
:
'
Metric
'
,
scale
:
true
},
series
:
trialIntermediate
series
:
dataForEchart
};
return
(
<
ReactEcharts
...
...
@@ -119,108 +125,92 @@ class Compare extends React.Component<CompareProps, {}> {
notMerge
=
{
true
}
// update now
/>
);
}
;
}
// render table column ---
initColumn
=
():
React
.
ReactNode
=>
{
const
idList
:
string
[]
=
[];
const
sequenceIdList
:
number
[]
=
[];
const
durationList
:
number
[]
=
[];
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
compareStacks
=
this
.
props
.
compareStacks
.
map
(
tableRecord
=>
TRIALS
.
getTrial
(
tableRecord
.
id
));
private
_overlapKeys
(
s
:
Map
<
string
,
any
>
[]):
string
[]
{
// Calculate the overlapped keys for multiple
const
intersection
:
string
[]
=
[];
for
(
const
i
of
s
[
0
].
keys
())
{
let
inAll
=
true
;
for
(
const
t
of
s
)
{
if
(
!
Array
.
from
(
t
.
keys
()).
includes
(
i
))
{
inAll
=
false
;
break
;
}
}
if
(
inAll
)
{
intersection
.
push
(
i
);
}
}
return
intersection
;
}
const
parameterList
:
Array
<
object
>
=
[];
let
parameterKeys
:
string
[]
=
[];
if
(
compareStacks
.
length
!==
0
)
{
parameterKeys
=
Object
.
keys
(
compareStacks
[
0
].
description
.
parameters
);
}
compareStacks
.
forEach
(
temp
=>
{
idList
.
push
(
temp
.
id
);
sequenceIdList
.
push
(
temp
.
sequenceId
);
durationList
.
push
(
temp
.
duration
);
parameterList
.
push
(
temp
.
description
.
parameters
);
});
let
isComplexSearchSpace
;
if
(
parameterList
.
length
>
0
)
{
isComplexSearchSpace
=
typeof
parameterList
[
0
][
parameterKeys
[
0
]]
===
'
object
'
?
true
:
false
;
}
const
width
=
this
.
getWebUIWidth
();
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
)
{
scrollClass
=
i
dList
.
length
>
3
?
'
flex
'
:
''
;
scrollClass
=
i
tems
.
length
>
3
?
'
flex
'
:
''
;
}
else
if
(
width
<
700
)
{
scrollClass
=
i
dList
.
length
>
1
?
'
flex
'
:
''
;
scrollClass
=
i
tems
.
length
>
1
?
'
flex
'
:
''
;
}
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
(
<
table
className
=
{
`compare-modal-table
${
scrollClass
}
`
}
>
<
tbody
>
<
tr
>
<
td
className
=
'column'
>
Id
</
td
>
{
Object
.
keys
(
idList
).
map
(
key
=>
(
<
td
className
=
'value idList'
key
=
{
key
}
>
{
idList
[
key
]
}
</
td
>
))
}
</
tr
>
<
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
>
))
}
{
this
.
_renderRow
(
'
id
'
,
'
ID
'
,
'
value idList
'
,
items
,
item
=>
item
.
id
)
}
{
this
.
_renderRow
(
'
trialnum
'
,
'
Trial No.
'
,
'
value
'
,
items
,
item
=>
item
.
sequenceId
.
toString
())
}
{
this
.
_renderRow
(
'
duration
'
,
'
Duration
'
,
'
value
'
,
items
,
item
=>
item
.
duration
)
}
{
parameterKeys
.
map
(
k
=>
this
.
_renderRow
(
`space_
${
k
}
`
,
k
,
'
value
'
,
items
,
item
=>
item
.
parameters
.
get
(
k
))
)
}
{
metricKeys
.
map
(
k
=>
this
.
_renderRow
(
`metrics_
${
k
}
`
,
`Metric:
${
k
}
`
,
'
value
'
,
items
,
item
=>
item
.
metrics
.
get
(
k
))
)
}
</
tbody
>
</
table
>
);
};
getWebUIWidth
=
():
number
=>
{
return
window
.
innerWidth
;
};
componentDidMount
():
void
{
this
.
_isCompareMount
=
true
;
}
componentWillUnmount
():
void
{
this
.
_isCompareMount
=
false
;
}
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
(
<
Modal
...
...
@@ -229,22 +219,23 @@ class Compare extends React.Component<CompareProps, {}> {
className
=
'compare-modal'
allowTouchBodyScroll
=
{
true
}
dragOptions
=
{
dragOptions
}
onDismiss
=
{
onHideDialog
}
>
<
div
>
<
div
className
=
{
contentStyles
.
header
}
>
<
span
>
Compare trials
</
span
>
<
span
>
{
title
}
</
span
>
<
IconButton
styles
=
{
iconButtonStyles
}
iconProps
=
{
{
iconName
:
'
Cancel
'
}
}
ariaLabel
=
'Close popup modal'
onClick
=
{
cancelFunc
}
onClick
=
{
onHideDialog
}
/>
</
div
>
<
Stack
className
=
'compare-modal-intermediate'
>
{
this
.
intermediate
(
)
}
{
this
.
_
intermediate
s
(
items
,
defaultMetricKey
)
}
<
Stack
className
=
'compare-yAxis'
>
# Intermediate result
</
Stack
>
</
Stack
>
<
Stack
>
{
this
.
initColumn
(
)
}
</
Stack
>
{
showDetails
&&
<
Stack
>
{
this
.
_columns
(
items
)
}
</
Stack
>
}
</
div
>
</
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
{
Dropdown
,
IDropdownOption
,
Stack
}
from
'
@fluentui/react
'
;
import
{
Dropdown
,
IDropdownOption
,
Stack
,
DefaultButton
}
from
'
@fluentui/react
'
;
import
ParCoords
from
'
parcoord-es
'
;
import
'
parcoord-es/dist/parcoords.css
'
;
import
*
as
React
from
'
react
'
;
...
...
@@ -9,12 +9,16 @@ import { filterByStatus } from '../../static/function';
import
{
TableObj
,
SingleAxis
,
MultipleAxes
}
from
'
../../static/interface
'
;
import
'
../../static/style/button.scss
'
;
import
'
../../static/style/para.scss
'
;
import
ChangeColumnComponent
from
'
../modals/ChangeColumnComponent
'
;
interface
ParaState
{
dimName
:
string
[];
selectedPercent
:
string
;
primaryMetricKey
:
string
;
noChart
:
boolean
;
customizeColumnsDialogVisible
:
boolean
;
availableDimensions
:
string
[];
chosenDimensions
:
string
[];
}
interface
ParaProps
{
...
...
@@ -45,7 +49,10 @@ class Para extends React.Component<ParaProps, ParaState> {
dimName
:
[],
primaryMetricKey
:
'
default
'
,
selectedPercent
:
'
1
'
,
noChart
:
true
noChart
:
true
,
customizeColumnsDialogVisible
:
false
,
availableDimensions
:
[],
chosenDimensions
:
[]
};
}
...
...
@@ -82,11 +89,24 @@ class Para extends React.Component<ParaProps, ParaState> {
}
render
():
React
.
ReactNode
{
const
{
selectedPercent
,
noChart
}
=
this
.
state
;
const
{
selectedPercent
,
noChart
,
customizeColumnsDialogVisible
,
availableDimensions
,
chosenDimensions
}
=
this
.
state
;
return
(
<
div
className
=
'parameter'
>
<
Stack
horizontal
className
=
'para-filter'
horizontalAlign
=
'end'
>
<
DefaultButton
text
=
'Add/Remove axes'
onClick
=
{
():
void
=>
{
this
.
setState
({
customizeColumnsDialogVisible
:
true
});
}
}
styles
=
{
{
root
:
{
marginRight
:
10
}
}
}
/>
<
Dropdown
selectedKey
=
{
selectedPercent
}
onChange
=
{
this
.
percentNum
}
...
...
@@ -101,6 +121,21 @@ class Para extends React.Component<ParaProps, ParaState> {
/>
{
this
.
finalKeysDropdown
()
}
</
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
}
/>
{
noChart
&&
<
div
className
=
'nodata'
>
No data
</
div
>
}
</
div
>
...
...
@@ -143,13 +178,13 @@ class Para extends React.Component<ParaProps, ParaState> {
private
renderParallelCoordinates
():
void
{
const
{
searchSpace
}
=
this
.
props
;
const
percent
=
parseFloat
(
this
.
state
.
selectedPercent
);
const
{
primaryMetricKey
}
=
this
.
state
;
const
{
primaryMetricKey
,
chosenDimensions
}
=
this
.
state
;
const
inferredSearchSpace
=
TRIALS
.
inferredSearchSpace
(
searchSpace
);
const
inferredMetricSpace
=
TRIALS
.
inferredMetricSpace
();
let
convertedTrials
=
this
.
getTrialsAsObjectList
(
inferredSearchSpace
,
inferredMetricSpace
);
const
dimensions
:
[
any
,
any
][]
=
[];
const
dimensions
:
[
string
,
any
][]
=
[];
let
colorDim
:
string
|
undefined
=
undefined
,
colorScale
:
any
=
undefined
;
// treat every axis as numeric to fit for brush
...
...
@@ -213,7 +248,11 @@ class Para extends React.Component<ParaProps, ParaState> {
}
this
.
pcs
.
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
)
{
this
.
pcs
.
margin
(
this
.
innerChartMargins
)
...
...
@@ -230,6 +269,12 @@ class Para extends React.Component<ParaProps, ParaState> {
if
(
firstRun
)
{
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
):
{}[]
{
...
...
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
{
Stack
,
DefaultButton
,
Dropdown
,
DetailsList
,
IDetailsListProps
,
DetailsListLayoutMode
,
PrimaryButton
,
Modal
,
IDropdownOption
,
IColumn
,
Icon
,
IDropdownOption
,
PrimaryButton
,
Selection
,
SelectionMode
,
IconButton
,
TooltipHost
,
IStackTokens
Stack
,
StackItem
,
TooltipHost
}
from
'
@fluentui/react
'
;
import
ReactPaginate
from
'
react-paginate
'
;
import
{
LineChart
,
blocked
,
copy
}
from
'
../buttons/Icon
'
;
import
{
MANAGER_IP
,
COLUMNPro
}
from
'
../../static/const
'
;
import
{
convertDuration
,
formatTimestamp
,
intermediateGraphOption
,
parseMetrics
}
from
'
../../static/function
'
;
import
React
from
'
react
'
;
import
{
EXPERIMENT
,
TRIALS
}
from
'
../../static/datamodel
'
;
import
{
TableRecord
,
TrialJobInfo
}
from
'
../../static/interface
'
;
const
Details
=
lazy
(()
=>
import
(
'
../overview/table/Details
'
));
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
{
convertDuration
,
formatTimestamp
}
from
'
../../static/function
'
;
import
{
TableObj
}
from
'
../../static/interface
'
;
import
'
../../static/style/search.scss
'
;
import
'
../../static/style/tableStatus.css
'
;
import
'
../../static/style/logPath.scss
'
;
import
'
../../static/style/table.scss
'
;
import
'
../../static/style/button.scss
'
;
import
'
../../static/style/logPath.scss
'
;
import
'
../../static/style/openRow.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
'
);
require
(
'
echarts/lib/chart/line
'
);
...
...
@@ -45,462 +43,379 @@ echarts.registerTheme('my_theme', {
color
:
'
#3c8dbc
'
});
const
horizontalGapStackTokens
:
IStackTokens
=
{
childrenGap
:
20
,
padding
:
10
type
SearchOptionType
=
'
id
'
|
'
trialnum
'
|
'
status
'
|
'
parameters
'
;
const
searchOptionLiterals
=
{
id
:
'
ID
'
,
trialnum
:
'
Trial No.
'
,
status
:
'
Status
'
,
parameters
:
'
Parameters
'
};
interface
TableListProps
{
pageSize
:
number
;
tableSource
:
Array
<
TableRecord
>
;
columnList
:
string
[];
// user select columnKeys
changeColumn
:
(
val
:
string
[])
=>
void
;
trialsUpdateBroadcast
:
number
;
}
const
defaultDisplayedColumns
=
[
'
sequenceId
'
,
'
id
'
,
'
duration
'
,
'
status
'
,
'
latestAccuracy
'
];
interface
SortInfo
{
field
:
string
;
isDescend
?:
boolean
;
}
function
_copyAndSort
<
T
>
(
items
:
T
[],
columnKey
:
string
,
isSortedDescending
?:
boolean
):
any
{
const
key
=
columnKey
as
keyof
T
;
return
items
.
slice
(
0
).
sort
(
function
(
a
:
T
,
b
:
T
):
any
{
if
(
a
[
key
]
===
undefined
||
Object
.
is
(
a
[
key
],
NaN
)
||
Object
.
is
(
a
[
key
],
Infinity
)
||
Object
.
is
(
a
[
key
],
-
Infinity
)
||
typeof
a
[
key
]
===
'
object
'
)
{
return
1
;
}
if
(
b
[
key
]
===
undefined
||
Object
.
is
(
b
[
key
],
NaN
)
||
Object
.
is
(
b
[
key
],
Infinity
)
||
Object
.
is
(
b
[
key
],
-
Infinity
)
||
typeof
b
[
key
]
===
'
object
'
)
{
return
-
1
;
}
return
(
isSortedDescending
?
a
[
key
]
<
b
[
key
]
:
a
[
key
]
>
b
[
key
])
?
1
:
-
1
;
});
}
function
_inferColumnTitle
(
columnKey
:
string
):
string
{
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
{
intermediateOption
:
object
;
modalVisible
:
boolean
;
isObjFinal
:
boolean
;
isShowColumn
:
boolean
;
selectRows
:
Array
<
any
>
;
isShowCompareModal
:
boolean
;
selectedRowKeys
:
string
[]
|
number
[];
intermediateData
:
Array
<
object
>
;
// a trial's intermediate results (include dict)
intermediateId
:
string
;
intermediateOtherKeys
:
string
[];
isShowCustomizedModal
:
boolean
;
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
;
displayedItems
:
any
[];
displayedColumns
:
string
[];
columns
:
IColumn
[];
searchType
:
SearchOptionType
;
searchText
:
string
;
selectedRowIds
:
string
[];
customizeColumnsDialogVisible
:
boolean
;
compareDialogVisible
:
boolean
;
intermediateDialogTrial
:
TableObj
|
undefined
;
copiedTrialId
:
string
|
undefined
;
sortInfo
:
SortInfo
;
}
class
TableList
extends
React
.
Component
<
TableListProps
,
TableListState
>
{
p
ublic
intervalTrialLog
=
10
;
p
ublic
t
rialId
!
:
string
;
p
rivate
_selection
:
Selection
;
p
rivate
_expandedT
rialId
s
:
Set
<
string
>
;
constructor
(
props
:
TableListProps
)
{
super
(
props
);
this
.
state
=
{
intermediateOption
:
{},
modalVisible
:
false
,
isObjFinal
:
false
,
isShowColumn
:
false
,
isShowCompareModal
:
false
,
selectRows
:
[],
selectedRowKeys
:
[],
// close selected trial message after modal closed
intermediateData
:
[],
intermediateId
:
''
,
intermediateOtherKeys
:
[],
isShowCustomizedModal
:
false
,
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
displayedItems
:
[],
displayedColumns
:
defaultDisplayedColumns
,
columns
:
[],
searchType
:
'
id
'
,
searchText
:
''
,
customizeColumnsDialogVisible
:
false
,
compareDialogVisible
:
false
,
selectedRowIds
:
[],
intermediateDialogTrial
:
undefined
,
copiedTrialId
:
undefined
,
sortInfo
:
{
field
:
''
,
isDescend
:
true
}
};
}
// sort for table column
onColumnClick
=
(
ev
:
React
.
MouseEvent
<
HTMLElement
>
,
getColumn
:
IColumn
):
void
=>
{
const
{
tableColumns
}
=
this
.
state
;
const
newColumns
:
IColumn
[]
=
tableColumns
.
slice
();
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
.
_selection
=
new
Selection
({
onSelectionChanged
:
():
void
=>
{
this
.
setState
({
selectedRowIds
:
this
.
_selection
.
getSelection
().
map
(
s
=>
(
s
as
any
).
id
)
});
}
});
this
.
setState
(
{
tableColumns
:
newColumns
,
sortMessage
:
{
field
:
getColumn
.
key
,
isDescend
:
currColumn
.
isSortedDescending
}
},
()
=>
{
this
.
updateData
();
this
.
_expandedTrialIds
=
new
Set
<
string
>
();
}
);
};
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
};
/* Search related methods */
IdColumnConfig
:
any
=
{
name
:
'
ID
'
,
key
:
'
id
'
,
fieldName
:
'
id
'
,
minWidth
:
150
,
maxWidth
:
200
,
isResizable
:
true
,
data
:
'
string
'
,
onColumnClick
:
this
.
onColumnClick
,
className
:
'
tableHead leftTitle
'
};
// This functions as the filter for the final trials displayed in the current table
private
_filterTrials
(
trials
:
TableObj
[]):
TableObj
[]
{
const
{
searchText
,
searchType
}
=
this
.
state
;
// search a trial by Trial No. | Trial ID | Parameters | Status
let
searchFilter
=
(
_
:
TableObj
):
boolean
=>
true
;
// eslint-disable-line no-unused-vars
if
(
searchText
.
trim
())
{
if
(
searchType
===
'
id
'
)
{
searchFilter
=
(
trial
):
boolean
=>
trial
.
id
.
toUpperCase
().
includes
(
searchText
.
toUpperCase
());
}
else
if
(
searchType
===
'
trialnum
'
)
{
searchFilter
=
(
trial
):
boolean
=>
trial
.
sequenceId
.
toString
()
===
searchText
;
}
else
if
(
searchType
===
'
status
'
)
{
searchFilter
=
(
trial
):
boolean
=>
trial
.
status
.
toUpperCase
().
includes
(
searchText
.
toUpperCase
());
}
else
if
(
searchType
===
'
parameters
'
)
{
// TODO: support filters like `x: 2` (instead of `'x': 2`)
searchFilter
=
(
trial
):
boolean
=>
JSON
.
stringify
(
trial
.
description
.
parameters
).
includes
(
searchText
);
}
}
return
trials
.
filter
(
searchFilter
);
}
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
>
};
private
_updateSearchFilterType
(
_event
:
React
.
FormEvent
<
HTMLDivElement
>
,
item
:
IDropdownOption
|
undefined
):
void
{
if
(
item
!==
undefined
)
{
const
value
=
item
.
key
.
toString
();
if
(
searchOptionLiterals
.
hasOwnProperty
(
value
))
{
this
.
setState
({
searchType
:
value
as
SearchOptionType
},
this
.
_updateTableSource
);
}
}
}
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
>
};
private
_updateSearchText
(
ev
:
React
.
ChangeEvent
<
HTMLInputElement
>
):
void
{
this
.
setState
({
searchText
:
ev
.
target
.
value
},
this
.
_updateTableSource
);
}
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
>
};
/* Table basic function related methods */
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
>
};
private
_onColumnClick
(
ev
:
React
.
MouseEvent
<
HTMLElement
>
,
column
:
IColumn
):
void
{
// handle the click events on table header (do sorting)
const
{
columns
}
=
this
.
state
;
const
newColumns
:
IColumn
[]
=
columns
.
slice
();
const
currColumn
:
IColumn
=
newColumns
.
filter
(
currCol
=>
column
.
key
===
currCol
.
key
)[
0
];
const
isSortedDescending
=
!
currColumn
.
isSortedDescending
;
this
.
setState
(
{
sortInfo
:
{
field
:
column
.
key
,
isDescend
:
isSortedDescending
}
},
this
.
_updateTableSource
);
}
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
>
private
_trialsToTableItems
(
trials
:
TableObj
[]):
any
[]
{
// TODO: use search space and metrics space from TRIALS will cause update issues.
const
searchSpace
=
TRIALS
.
inferredSearchSpace
(
EXPERIMENT
.
searchSpaceNew
);
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 `_`
};
showIntermediateModal
=
async
(
record
:
TrialJobInfo
,
event
:
React
.
SyntheticEvent
<
EventTarget
>
):
Promise
<
void
>
=>
{
event
.
preventDefault
();
event
.
stopPropagation
();
const
res
=
await
axios
.
get
(
`
${
MANAGER_IP
}
/metric-data/
${
record
.
id
}
`
);
if
(
res
.
status
===
200
)
{
const
intermediateArr
:
number
[]
=
[];
// support intermediate result is dict because the last intermediate result is
// final result in a succeed trial, it may be a dict.
// get intermediate result dict keys array
const
{
intermediateKey
}
=
this
.
state
;
const
otherkeys
:
string
[]
=
[];
const
metricDatas
=
res
.
data
;
if
(
metricDatas
.
length
!==
0
)
{
// just add type=number keys
const
intermediateMetrics
=
parseMetrics
(
metricDatas
[
0
].
data
);
for
(
const
key
in
intermediateMetrics
)
{
if
(
typeof
intermediateMetrics
[
key
]
===
'
number
'
)
{
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
);
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
;
});
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
});
};
// intermediate button click -> intermediate graph for each trial
// support intermediate is dict
selectOtherKeys
=
(
event
:
React
.
FormEvent
<
HTMLDivElement
>
,
item
?:
IDropdownOption
):
void
=>
{
if
(
item
!==
undefined
)
{
const
value
=
item
.
text
;
const
isShowDefault
:
boolean
=
value
===
'
default
'
?
true
:
false
;
const
{
intermediateData
,
intermediateId
}
=
this
.
state
;
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
]);
const
{
sortInfo
}
=
this
.
state
;
if
(
sortInfo
.
field
!==
''
)
{
return
_copyAndSort
(
items
,
sortInfo
.
field
,
sortInfo
.
isDescend
);
}
else
{
intermediateArr
.
push
(
tem
p
)
;
return
i
tem
s
;
}
}
});
private
_buildColumnsFromTableItems
(
tableItems
:
any
[]):
IColumn
[]
{
// extra column, for a icon to expand the trial details panel
const
columns
:
IColumn
[]
=
[
{
key
:
'
_expand
'
,
name
:
''
,
onRender
:
(
item
,
index
):
any
=>
{
return
(
<
Icon
aria
-
hidden
=
{
true
}
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
{
Object
.
keys
(
intermediateData
).
map
(
item
=>
{
const
temp
=
parseMetrics
(
intermediateData
[
item
].
data
);
if
(
typeof
temp
===
'
object
'
)
{
intermediateArr
.
push
(
temp
[
value
]);
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
}
const
intermediate
=
intermediateGraphOption
(
intermediateArr
,
intermediateId
);
// re-render
this
.
setState
({
intermediateKey
:
value
,
intermediateOption
:
intermediate
];
// looking at the first row only for now
for
(
const
k
of
Object
.
keys
(
tableItems
[
0
]))
{
if
(
k
===
'
metric/default
'
)
{
// FIXME: default metric is hacked as latestAccuracy currently
continue
;
}
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
>
)
})
});
}
};
hideIntermediateModal
=
():
void
=>
{
this
.
setState
({
modalVisible
:
false
// operations column
columns
.
push
({
name
:
'
Operation
'
,
key
:
'
_operation
'
,
fieldName
:
'
operation
'
,
minWidth
:
160
,
maxWidth
:
200
,
isResizable
:
true
,
className
:
'
detail-table
'
,
onRender
:
this
.
_renderOperationColumn
.
bind
(
this
)
});
};
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
compareBtn
=
():
void
=>
{
const
{
selectRows
}
=
this
.
state
;
if
(
selectRows
.
length
===
0
)
{
alert
(
'
Please select datas you want to compare!
'
);
const
{
sortInfo
}
=
this
.
state
;
for
(
const
column
of
columns
)
{
if
(
column
.
key
===
sortInfo
.
field
)
{
column
.
isSorted
=
true
;
column
.
isSortedDescending
=
sortInfo
.
isDescend
;
}
else
{
this
.
setState
({
isShowCompareModal
:
true
});
column
.
isSorted
=
false
;
column
.
isSortedDescending
=
true
;
}
}
return
columns
;
}
};
// close Compare-modal
hideCompareModal
=
():
void
=>
{
// close modal. clear select rows data, clear selected track
this
.
setState
({
isShowCompareModal
:
false
,
selectedRowKeys
:
[],
selectRows
:
[]
});
};
// open customized trial modal
private
setCustomizedTrial
=
(
trialId
:
string
,
event
:
React
.
SyntheticEvent
<
EventTarget
>
):
void
=>
{
event
.
preventDefault
();
event
.
stopPropagation
();
private
_updateTableSource
():
void
{
// call this method when trials or the computation of trial filter has changed
const
items
=
this
.
_trialsToTableItems
(
this
.
_filterTrials
(
this
.
props
.
tableSource
));
if
(
items
.
length
>
0
)
{
const
columns
=
this
.
_buildColumnsFromTableItems
(
items
);
this
.
setState
({
isShowCustomizedModal
:
true
,
copyTrialId
:
trialId
displayedItems
:
items
,
columns
:
columns
});
};
private
closeCustomizedTrial
=
():
void
=>
{
}
else
{
this
.
setState
({
isShowCustomizedModal
:
false
,
copyTrialId
:
''
displayedItems
:
[]
,
columns
:
[]
});
};
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
({
onSelectionChanged
:
():
void
=>
{
this
.
setState
(()
=>
({
selectRows
:
this
.
getSelectedRows
.
getSelection
()
}));
}
});
// 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)`
);
private
_updateDisplayedColumns
(
displayedColumns
:
string
[]):
void
{
this
.
setState
({
displayedColumns
:
displayedColumns
});
}
}
// 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[]
// when user click [Add Column] need to use the function
private
initTableColumnList
=
(
columnList
:
string
[]):
IColumn
[]
=>
{
// const { columnList } = this.props;
private
_renderOperationColumn
(
record
:
any
):
React
.
ReactNode
{
const
runningTrial
:
boolean
=
[
'
RUNNING
'
,
'
UNKNOWN
'
].
includes
(
record
.
status
)
?
false
:
true
;
const
disabledAddCustomizedTrial
=
[
'
DONE
'
,
'
ERROR
'
,
'
STOPPED
'
].
includes
(
EXPERIMENT
.
status
);
const
showColumn
:
IColumn
[]
=
[];
for
(
const
item
of
columnList
)
{
const
paraColumn
=
item
.
match
(
/
\(
search space
\)
$/
);
let
result
;
if
(
paraColumn
!==
null
)
{
result
=
paraColumn
.
input
;
}
switch
(
item
)
{
case
'
Trial No.
'
:
showColumn
.
push
(
this
.
SequenceIdColumnConfig
);
break
;
case
'
ID
'
:
showColumn
.
push
(
this
.
IdColumnConfig
);
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
)
}
onClick
=
{
():
void
=>
{
const
{
tableSource
}
=
this
.
props
;
const
trial
=
tableSource
.
find
(
trial
=>
trial
.
id
===
record
.
id
)
as
TableObj
;
this
.
setState
({
intermediateDialogTrial
:
trial
});
}
}
>
{
LineChart
}
</
PrimaryButton
>
{
/* kill job */
}
{
flag
?
(
{
runningTrial
?
(
<
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
)
}
onClick
=
{
():
void
=>
{
this
.
setState
({
copiedTrialId
:
record
.
id
});
}
}
disabled
=
{
disabledAddCustomizedTrial
}
>
{
copy
}
...
...
@@ -508,286 +423,138 @@ class TableList extends React.Component<TableListProps, TableListState> {
</
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
>
);
}
});
}
}
return
showColumn
;
};
componentDidMount
():
void
{
window
.
addEventListener
(
'
resize
'
,
this
.
onWindowResize
);
this
.
updateData
();
}
componentDidUpdate
(
prevProps
:
TableListProps
):
void
{
if
(
this
.
props
.
columnList
!==
prevProps
.
columnList
||
this
.
props
.
tableSource
!==
prevProps
.
tableSource
||
prevProps
.
trialsUpdateBroadcast
!==
this
.
props
.
trialsUpdateBroadcast
)
{
const
{
columnList
}
=
this
.
props
;
this
.
setState
(
{
tableColumns
:
this
.
initTableColumnList
(
columnList
),
allColumnList
:
this
.
getAllColumnKeys
()
},
()
=>
{
this
.
updateData
();
}
);
if
(
this
.
props
.
tableSource
!==
prevProps
.
tableSource
)
{
this
.
_updateTableSource
();
}
}
// slice all table data into current page data
updateData
():
void
{
const
tableSource
:
Array
<
TableRecord
>
=
this
.
props
.
tableSource
;
const
{
offset
,
perPage
,
sortMessage
}
=
this
.
state
;
if
(
sortMessage
.
field
!==
''
)
{
tableSource
.
sort
(
function
(
a
,
b
):
any
{
if
(
a
[
sortMessage
.
field
]
===
undefined
||
Object
.
is
(
a
[
sortMessage
.
field
],
NaN
)
||
Object
.
is
(
a
[
sortMessage
.
field
],
Infinity
)
||
Object
.
is
(
a
[
sortMessage
.
field
],
-
Infinity
)
||
typeof
a
[
sortMessage
.
field
]
===
'
object
'
)
{
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
);
const
curPageCount
=
Math
.
ceil
(
tableSource
.
length
/
perPage
);
this
.
setState
({
tablePerPage
:
tableSlice
,
pageCount
:
curPageCount
});
}
// update data when click the page index of pagination
handlePageClick
=
(
evt
:
any
):
void
=>
{
const
selectedPage
=
evt
.
selected
;
const
offset
=
selectedPage
*
this
.
state
.
perPage
;
this
.
setState
(
{
currentPage
:
selectedPage
,
offset
:
offset
},
()
=>
{
this
.
updateData
();
}
);
};
// 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
(
{
perPage
:
currentPerPage
,
offset
:
0
,
currentPage
:
0
,
pageCount
:
currentPageCount
},
()
=>
{
this
.
updateData
();
}
);
componentDidMount
():
void
{
this
.
_updateTableSource
();
}
};
render
():
React
.
ReactNode
{
const
{
intermediateKey
,
modalIntermediateWidth
,
modalIntermediateHeight
,
tableColumns
,
allColumnList
,
isShowColumn
,
modalVisible
,
selectRows
,
isShowCompareModal
,
intermediateOtherKeys
,
isShowCustomizedModal
,
copyTrialId
,
intermediateOption
,
tablePerPage
displayedItems
,
columns
,
searchType
,
customizeColumnsDialogVisible
,
compareDialogVisible
,
displayedColumns
,
selectedRowIds
,
intermediateDialogTrial
,
copiedTrialId
}
=
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
(
<
Stack
>
<
div
id
=
'tableList'
>
<
DetailsList
columns
=
{
tableColumns
}
items
=
{
tablePerPage
}
setKey
=
'set'
compact
=
{
true
}
onRenderRow
=
{
this
.
onRenderRow
}
layoutMode
=
{
DetailsListLayoutMode
.
justified
}
selectionMode
=
{
SelectionMode
.
multiple
}
selection
=
{
this
.
getSelectedRows
}
<
Stack
horizontal
className
=
'panelTitle'
style
=
{
{
marginTop
:
10
}
}
>
<
span
style
=
{
{
marginRight
:
12
}
}
>
{
tableListIcon
}
</
span
>
<
span
>
Trial jobs
</
span
>
</
Stack
>
<
Stack
horizontal
className
=
'allList'
>
<
StackItem
grow
=
{
50
}
>
<
DefaultButton
text
=
'Compare'
className
=
'allList-compare'
onClick
=
{
():
void
=>
{
this
.
setState
({
compareDialogVisible
:
true
});
}
}
disabled
=
{
selectedRowIds
.
length
===
0
}
/>
</
StackItem
>
<
StackItem
grow
=
{
50
}
>
<
Stack
horizontal
horizontalAlign
=
'end'
className
=
'allList'
>
<
DefaultButton
className
=
'allList-button-gap'
text
=
'Add/Remove columns'
onClick
=
{
():
void
=>
{
this
.
setState
({
customizeColumnsDialogVisible
:
true
});
}
}
/>
<
Stack
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
}
}
}
selectedKey
=
{
searchType
}
options
=
{
Object
.
entries
(
searchOptionLiterals
).
map
(([
k
,
v
])
=>
({
key
:
k
,
text
:
v
}))
}
onChange
=
{
this
.
_updateSearchFilterType
.
bind
(
this
)
}
styles
=
{
{
root
:
{
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
}
<
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
>
</
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
}
</
StackItem
>
</
Stack
>
{
columns
&&
displayedItems
&&
(
<
PaginationTable
columns
=
{
columns
.
filter
(
column
=>
displayedColumns
.
includes
(
column
.
key
)
||
[
'
_expand
'
,
'
_operation
'
].
includes
(
column
.
key
)
)
}
items
=
{
displayedItems
}
compact
=
{
true
}
selection
=
{
this
.
_selection
}
selectionMode
=
{
SelectionMode
.
multiple
}
selectionPreservedOnEmptyClick
=
{
true
}
onRenderRow
=
{
(
props
):
any
=>
{
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return
<
ExpandableDetails
detailsProps
=
{
props
!
}
isExpand
=
{
props
!
.
item
.
_expandDetails
}
/>;
}
}
/>
</
div
>
{
intermediateOtherKeys
.
length
>
1
?
(
<
Stack
horizontalAlign
=
'end'
className
=
'selectKeys'
>
<
Dropdown
className
=
'select'
selectedKey
=
{
intermediateKey
}
options
=
{
intermediateOtherKeys
.
map
((
key
,
item
)
=>
{
return
{
key
:
key
,
text
:
intermediateOtherKeys
[
item
]
};
})
}
onChange
=
{
this
.
selectOtherKeys
}
)
}
{
compareDialogVisible
&&
(
<
Compare
title
=
'Compare trials'
showDetails
=
{
true
}
trials
=
{
this
.
props
.
tableSource
.
filter
(
trial
=>
selectedRowIds
.
includes
(
trial
.
id
))
}
onHideDialog
=
{
():
void
=>
{
this
.
setState
({
compareDialogVisible
:
false
});
}
}
/>
</
Stack
>
)
:
null
}
<
div
className
=
'intermediate-graph'
>
<
ReactEcharts
option
=
{
intermediateOption
}
style
=
{
{
width
:
0.5
*
modalIntermediateWidth
,
height
:
0.7
*
modalIntermediateHeight
,
maxHeight
:
534
,
padding
:
20
)
}
{
intermediateDialogTrial
!==
undefined
&&
(
<
Compare
title
=
'Intermediate results'
showDetails
=
{
false
}
trials
=
{
[
intermediateDialogTrial
]
}
onHideDialog
=
{
():
void
=>
{
this
.
setState
({
intermediateDialogTrial
:
undefined
});
}
}
theme
=
'my_theme'
/>
<
div
className
=
'xAxis'
>
#Intermediate result
</
div
>
</
div
>
</
Modal
>
{
/* Add Column Modal */
}
{
isShowColumn
&&
(
)
}
{
customizeColumnsDialogVisible
&&
(
<
ChangeColumnComponent
hideShowColumnDialog
=
{
this
.
hideShowColumnModal
}
isHideDialog
=
{
!
isShowColumn
}
showColumn
=
{
allColumnList
}
selectedColumn
=
{
columnList
}
changeColumn
=
{
this
.
props
.
changeColumn
}
selectedColumns
=
{
displayedColumns
}
allColumns
=
{
columns
.
filter
(
column
=>
!
column
.
key
.
startsWith
(
'
_
'
))
.
map
(
column
=>
({
key
:
column
.
key
,
name
:
column
.
name
}))
}
onSelectedChange
=
{
this
.
_updateDisplayedColumns
.
bind
(
this
)
}
onHideDialog
=
{
():
void
=>
{
this
.
setState
({
customizeColumnsDialogVisible
:
false
});
}
}
/>
)
}
{
/* compare trials based message */
}
{
isShowCompareModal
&&
<
Compare
compareStacks
=
{
selectRows
}
cancelFunc
=
{
this
.
hideCompareModal
}
/>
}
{
/* clone trial parameters and could submit a customized trial */
}
{
/* Clone a trial and customize a set of new parameters */
}
{
/* visible is done inside because prompt is needed even when the dialog is closed */
}
<
Customize
visible
=
{
isShowCustomizedModal
}
copyTrialId
=
{
copyTrialId
}
closeCustomizeModal
=
{
this
.
closeCustomizedTrial
}
visible
=
{
copiedTrialId
!==
undefined
}
copyTrialId
=
{
copiedTrialId
||
''
}
closeCustomizeModal
=
{
():
void
=>
{
this
.
setState
({
copiedTrialId
:
undefined
});
}
}
/>
</
Stack
>
</
div
>
);
}
}
...
...
src/webui/src/static/interface.ts
View file @
88a225f8
...
...
@@ -33,6 +33,7 @@ interface TableObj {
color
?:
string
;
startTime
?:
number
;
endTime
?:
number
;
intermediates
:
(
MetricDataRecord
|
undefined
)[];
parameters
(
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(
class
Trial
implements
TableObj
{
private
metricsInitialized
:
boolean
=
false
;
private
infoField
:
TrialJobInfo
|
undefined
;
p
rivate
intermediates
:
(
MetricDataRecord
|
undefined
)[]
=
[];
p
ublic
intermediates
:
(
MetricDataRecord
|
undefined
)[]
=
[];
public
final
:
MetricDataRecord
|
undefined
;
private
finalAcc
:
number
|
undefined
;
...
...
@@ -224,24 +224,29 @@ class Trial implements TableObj {
}
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
)
{
throw
new
Map
()
;
throw
ret
;
}
else
{
const
tempHyper
=
this
.
info
.
hyperParameters
;
let
params
=
JSON
.
parse
(
tempHyper
[
tempHyper
.
length
-
1
]).
parameters
;
if
(
typeof
params
===
'
string
'
)
{
params
=
JSON
.
parse
(
params
);
}
const
[
result
,
unexpectedEntries
]
=
inferTrialParameters
(
params
,
axes
);
const
[
updated
,
unexpectedEntries
]
=
inferTrialParameters
(
params
,
axes
);
if
(
unexpectedEntries
.
size
)
{
throw
unexpectedEntries
;
}
return
result
;
for
(
const
[
k
,
v
]
of
updated
)
{
ret
.
set
(
k
,
v
);
}
return
ret
;
}
}
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
>
();
if
(
this
.
acc
===
undefined
)
{
return
ret
;
...
...
src/webui/src/static/style/table.scss
View file @
88a225f8
...
...
@@ -58,7 +58,7 @@
}
.detail-table
{
padding
:
5px
0
0
0
;
padding
-top
:
5px
;
}
.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