Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
nomad-lab
nomad-FAIR
Commits
f60f18cc
Commit
f60f18cc
authored
Apr 28, 2020
by
Markus Scheidgen
Browse files
Refactored the date histogram.
parent
16dd821a
Pipeline
#73975
failed with stages
in 2 minutes and 9 seconds
Changes
5
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
gui/src/components/search/Search.js
View file @
f60f18cc
...
...
@@ -15,7 +15,7 @@ import UploadList from './UploadsList'
import
GroupList
from
'
./GroupList
'
import
ApiDialogButton
from
'
../ApiDialogButton
'
import
SearchIcon
from
'
@material-ui/icons/Search
'
import
Uploads
Chart
from
'
./UploadsChart
'
import
Uploads
Histogram
from
'
./UploadsChart
'
import
QuantityHistogram
from
'
./QuantityHistogram
'
import
SearchContext
,
{
searchContext
}
from
'
./SearchContext
'
import
{
objectFilter
}
from
'
../../utils
'
...
...
@@ -213,17 +213,13 @@ SearchEntry.propTypes = {
ownerTypes
:
PropTypes
.
arrayOf
(
PropTypes
.
string
)
}
function
UsersVisualization
(
props
)
{
const
{
domain
,
response
:
{
metric
},
setStatistics
}
=
useContext
(
searchContext
)
function
UsersVisualization
()
{
const
{
setStatistics
}
=
useContext
(
searchContext
)
useEffect
(()
=>
{
setStatistics
([
'
uploader
'
])
},
[])
return
<
div
>
<
Card
>
<
CardContent
>
<
UploadsChart
metricsDefinitions
=
{
domain
.
searchMetrics
}
/
>
<
/CardContent
>
<
/Card
>
<
UploadsHistogram
/>
<
QuantityHistogram
quantity
=
"
uploader
"
title
=
"
Uploaders
"
/>
<
/div
>
}
...
...
gui/src/components/search/SearchContext.js
View file @
f60f18cc
...
...
@@ -9,6 +9,24 @@ import { useLocation, useHistory } from 'react-router-dom'
import
qs
from
'
qs
'
import
*
as
searchQuantities
from
'
../../searchQuantities.json
'
const
padDateNumber
=
number
=>
String
(
'
00
'
+
number
).
slice
(
-
2
)
export
const
Dates
=
{
dateHistogramStartDate
:
'
2014-12-15
'
,
APIDate
:
date
=>
date
.
toISOString
(),
JSDate
:
date
=>
new
Date
(
date
),
FormDate
:
date
=>
{
date
=
new
Date
(
date
)
return
`
${
date
.
getFullYear
()}
-
${
padDateNumber
(
date
.
getMonth
())}
-
${
padDateNumber
(
date
.
getDate
())}
`
},
addSeconds
:
(
date
,
interval
)
=>
new
Date
(
date
.
getTime
()
+
interval
*
1000
),
deltaSeconds
:
(
from
,
end
)
=>
Math
.
round
((
new
Date
(
end
).
getTime
()
-
new
Date
(
from
).
getTime
())
/
1000
),
intervalSeconds
:
(
from
,
end
,
buckets
)
=>
Math
.
round
((
new
Date
(
end
).
getTime
()
-
new
Date
(
from
).
getTime
())
/
(
1000
*
buckets
)),
buckets
:
50
}
searchQuantities
[
'
from_time
'
]
=
true
searchQuantities
[
'
until_time
'
]
=
true
/**
* A custom hook that reads and writes search parameters from the current URL.
*/
...
...
@@ -115,7 +133,8 @@ export default function SearchContext({initialRequest, initialQuery, query, chil
// checks for necessity. It will update the response state, once the request has
// been answered by the api.
const
runRequest
=
useCallback
(()
=>
{
const
{
metric
,
domainKey
,
owner
}
=
requestRef
.
current
let
dateHistogramInterval
=
null
const
{
metric
,
domainKey
,
owner
,
dateHistogram
}
=
requestRef
.
current
const
domain
=
domains
[
domainKey
]
const
apiRequest
=
{
...
initialRequest
,
...
...
@@ -132,9 +151,23 @@ export default function SearchContext({initialRequest, initialQuery, query, chil
...
requestRef
.
current
.
query
,
...
query
}
if
(
dateHistogram
)
{
dateHistogramInterval
=
Dates
.
intervalSeconds
(
apiQuery
.
from_time
||
Dates
.
dateHistogramStartDate
,
apiQuery
.
until_time
||
new
Date
(),
Dates
.
buckets
)
apiQuery
[
'
date_histogram
'
]
=
true
apiQuery
[
'
interval
'
]
=
`
${
dateHistogramInterval
}
s`
}
api
.
search
(
apiQuery
)
.
then
(
newResponse
=>
{
setResponse
({...
emptyResponse
,
...
newResponse
,
metric
:
metric
})
setResponse
({
...
emptyResponse
,
...
newResponse
,
metric
:
metric
,
dateHistogramInterval
:
dateHistogramInterval
,
from_time
:
apiQuery
.
from_time
,
until_time
:
apiQuery
.
until_time
})
}).
catch
(
error
=>
{
setResponse
({...
emptyResponse
,
metric
:
metric
})
raiseError
(
error
)
...
...
@@ -174,6 +207,10 @@ export default function SearchContext({initialRequest, initialQuery, query, chil
requestRef
.
current
.
groups
=
{...
groups
}
},
[
requestRef
])
const
setDateHistogram
=
useCallback
(
dateHistogram
=>
{
requestRef
.
current
.
dateHistogram
=
dateHistogram
},
[
requestRef
])
const
handleQueryChange
=
(
changes
,
replace
)
=>
{
if
(
changes
.
atoms
&&
changes
.
atoms
.
length
===
0
)
{
changes
.
atoms
=
undefined
...
...
@@ -219,6 +256,7 @@ export default function SearchContext({initialRequest, initialQuery, query, chil
setOwner
:
setOwner
,
setStatisticsToRefresh
:
()
=>
null
,
// TODO remove
setStatistics
:
setStatistics
,
setDateHistogram
:
setDateHistogram
,
update
:
runRequest
}
...
...
gui/src/components/search/UploadsChart.js
View file @
f60f18cc
import
React
from
'
react
'
import
React
,
{
useContext
,
useState
,
useEffect
,
useRef
,
useLayoutEffect
,
useCallback
}
from
'
react
'
import
PropTypes
from
'
prop-types
'
import
{
withStyles
,
Select
,
MenuItem
}
from
'
@material-ui/core
'
import
Button
from
'
@material-ui/core/Button
'
import
RefreshIcon
from
'
@material-ui/icons/Refresh
'
import
{
Select
,
MenuItem
,
Card
,
CardHeader
,
CardContent
,
makeStyles
}
from
'
@material-ui/core
'
import
Grid
from
'
@material-ui/core/Grid
'
import
TextField
from
'
@material-ui/core/TextField
'
import
*
as
d3
from
'
d3
'
import
{
scale
Band
,
scalePow
}
from
'
d3-scale
'
import
{
scale
Time
,
scalePow
}
from
'
d3-scale
'
import
{
nomadSecondaryColor
}
from
'
../../config.js
'
import
{
searchContext
}
from
'
./SearchContext
'
import
{
compose
}
from
'
recompose
'
import
{
withApi
}
from
'
../api
'
class
UploadsHistogramUnstyled
extends
React
.
Component
{
static
propTypes
=
{
classes
:
PropTypes
.
object
.
isRequired
,
height
:
PropTypes
.
number
.
isRequired
,
data
:
PropTypes
.
object
,
metric
:
PropTypes
.
string
.
isRequired
,
metricsDefinitions
:
PropTypes
.
object
.
isRequired
,
onChanged
:
PropTypes
.
func
.
isRequired
,
defaultScale
:
PropTypes
.
number
}
static
styles
=
theme
=>
({
root
:
{},
content
:
{
paddingTop
:
10
}
})
constructor
(
props
)
{
super
(
props
)
this
.
state
=
{
scalePower
:
this
.
props
.
defaultScale
||
1.0
,
time
:
null
,
from_time
:
0
,
until_time
:
0
}
this
.
container
=
React
.
createRef
()
this
.
svgEl
=
React
.
createRef
()
}
startDate
=
'
2013-01-01
'
scales
=
[
{
label
:
'
Linear
'
,
value
:
1.0
},
{
label
:
'
1/2
'
,
value
:
0.5
},
{
label
:
'
1/4
'
,
value
:
0.25
},
{
label
:
'
1/8
'
,
value
:
0.25
}
]
componentDidMount
()
{
const
from_time
=
new
Date
(
this
.
startDate
).
getTime
()
const
until_time
=
new
Date
().
getTime
()
this
.
handleTimeChange
(
from_time
,
'
from_time
'
,
'
all
'
)
this
.
handleTimeChange
(
until_time
,
'
until_time
'
,
'
all
'
)
}
componentDidUpdate
()
{
this
.
updateChart
()
import
{
searchContext
,
Dates
}
from
'
./SearchContext
'
const
useStyles
=
makeStyles
(
theme
=>
({
root
:
{
marginTop
:
theme
.
spacing
(
2
)
},
content
:
{
paddingTop
:
0
,
position
:
'
relative
'
,
height
:
250
},
tooltip
:
{
textAlign
:
'
center
'
,
position
:
'
absolute
'
,
pointerEvents
:
'
none
'
,
opacity
:
0
},
tooltipContent
:
{
// copy of the material ui popper style
display
:
'
inline-block
'
,
color
:
'
#fff
'
,
padding
:
'
4px 8px
'
,
fontSize
:
'
0.625rem
'
,
fontFamily
:
'
"Roboto", "Helvetica", "Arial", sans-serif
'
,
lineHeight
:
'
1.4em
'
,
borderRadius
:
'
4px
'
,
backgroundColor
:
'
#616161
'
}
handleQueryChange
()
{
const
from_time
=
new
Date
(
this
.
state
.
from_time
)
const
until_time
=
new
Date
(
this
.
state
.
until_time
)
this
.
props
.
onChanged
(
from_time
.
toISOString
(),
until_time
.
toISOString
())
}
handleTimeChange
(
newTime
,
key
,
target
)
{
let
date
if
(
!
newTime
)
{
date
=
key
===
'
from_time
'
?
new
Date
(
this
.
startDate
)
:
new
Date
()
}
else
{
date
=
new
Date
(
newTime
)
}))
export
default
function
UploadsHistogram
({
title
=
'
Uploads over time
'
,
initialScale
=
1
,
tooltips
})
{
const
classes
=
useStyles
()
const
containerRef
=
useRef
()
const
fromTimeFieldRef
=
useRef
()
const
untilTimeFieldRef
=
useRef
()
const
[
scale
,
setScale
]
=
useState
(
initialScale
)
const
{
response
,
query
,
setQuery
,
domain
,
setDateHistogram
}
=
useContext
(
searchContext
)
useEffect
(()
=>
{
setDateHistogram
(
true
)
return
()
=>
{
setDateHistogram
(
false
)
}
},
[])
if
(
target
===
'
state
'
||
target
===
'
all
'
)
{
if
(
key
===
'
from_time
'
)
{
if
(
this
.
state
.
from_time
!==
date
.
getTime
())
{
this
.
setState
({
from_time
:
date
.
getTime
()})
}
}
else
if
(
key
===
'
until_time
'
)
{
if
(
this
.
state
.
until_time
!==
date
.
getTime
())
{
this
.
setState
({
until_time
:
date
.
getTime
()})
}
}
}
if
(
target
===
'
picker
'
||
target
===
'
all
'
)
{
document
.
getElementById
(
key
).
value
=
date
.
toISOString
().
substring
(
0
,
10
)
}
}
handleItemClicked
(
item
,
deltaT
)
{
const
selected
=
item
.
time
if
(
selected
===
this
.
state
.
time
)
{
this
.
props
.
onChanged
(
null
,
null
)
}
else
{
this
.
handleTimeChange
(
selected
,
'
from_time
'
,
'
all
'
)
this
.
handleTimeChange
(
selected
+
deltaT
,
'
until_time
'
,
'
all
'
)
this
.
handleQueryChange
()
}
}
resolveDate
(
name
,
deltaT
)
{
const
date
=
new
Date
(
parseInt
(
name
,
10
))
const
year
=
date
.
toLocaleDateString
(
undefined
,
{
year
:
'
numeric
'
})
const
quarter
=
Math
.
floor
((
date
.
getMonth
()
+
3
)
/
3
)
const
month
=
date
.
toLocaleDateString
(
undefined
,
{
month
:
'
short
'
})
const
week
=
(
date
)
=>
{
const
first
=
new
Date
(
date
.
getFullYear
(),
0
,
1
)
return
Math
.
ceil
((((
date
-
first
)
/
86400000
)
+
first
.
getDay
()
+
1
)
/
7
)
}
const
day
=
date
.
toLocaleDateString
(
undefined
,
{
day
:
'
numeric
'
})
const
hour
=
date
.
toLocaleTimeString
(
undefined
,
{
hour
:
'
numeric
'
})
const
min
=
date
.
toLocaleTimeString
(
undefined
,
{
minute
:
'
numeric
'
})
const
sec
=
date
.
toLocaleTimeString
(
undefined
,
{
second
:
'
numeric
'
})
const
times
=
[
31536000
,
7776000
,
2419200
,
604800
,
864000
,
3600
,
60
,
1
]
const
diffs
=
times
.
map
(
t
=>
Math
.
abs
(
t
-
(
deltaT
/
1000
)))
useLayoutEffect
(()
=>
{
fromTimeFieldRef
.
current
.
value
=
Dates
.
FormDate
(
query
.
from_time
||
Dates
.
dateHistogramStartDate
)
untilTimeFieldRef
.
current
.
value
=
Dates
.
FormDate
(
query
.
until_time
||
new
Date
())
})
const
intervals
=
[
year
,
'
Q
'
+
quarter
,
month
,
'
W
'
+
week
,
day
,
hour
,
min
,
sec
]
return
intervals
[
diffs
.
indexOf
(
Math
.
min
(...
diffs
))]
}
useEffect
(()
=>
{
const
{
statistics
,
metric
}
=
response
updateChart
()
{
let
data
=
[]
if
(
!
this
.
props
.
data
)
{
if
(
!
statistics
.
date_histogram
)
{
return
}
else
{
data
=
Object
.
keys
(
this
.
props
.
data
).
map
(
key
=>
({
time
:
parseInt
(
key
,
10
),
// name: this.resolveDate(key),
value
:
this
.
props
.
data
[
key
][
this
.
props
.
metric
]
data
=
Object
.
keys
(
statistics
.
date_histogram
).
map
(
key
=>
({
time
:
Dates
.
JSDate
(
parseInt
(
key
)),
value
:
statistics
.
date_histogram
[
key
][
metric
]
}))
}
data
.
sort
((
a
,
b
)
=>
d3
.
ascending
(
a
.
time
,
b
.
time
))
if
(
data
.
length
>
0
)
{
this
.
handleTimeChange
(
this
.
state
.
from_time
,
'
from_time
'
,
'
picker
'
)
this
.
handleTimeChange
(
this
.
state
.
until_time
,
'
until_time
'
,
'
picker
'
)
}
const
fromTime
=
Dates
.
JSDate
(
response
.
from_time
||
Dates
.
dateHistogramStartDate
)
const
untilTime
=
Dates
.
JSDate
(
response
.
until_time
||
new
Date
())
const
interval
=
response
.
dateHistogramInterval
const
clickable
=
(
interval
*
Dates
.
buckets
)
>
3600
let
deltaT
=
31536000000
if
(
data
.
length
>
1
)
{
deltaT
=
data
[
1
].
time
-
data
[
0
].
time
const
handleItemClicked
=
item
=>
{
if
(
!
clickable
)
{
return
}
const
fromTime
=
item
.
time
const
untilTime
=
Dates
.
addSeconds
(
fromTime
,
interval
)
setQuery
({
...
query
,
from_time
:
Dates
.
APIDate
(
fromTime
),
until_time
:
Dates
.
APIDate
(
untilTime
)
})
}
data
.
forEach
(
d
=>
{
d
.
name
=
this
.
resolveDate
(
d
.
time
,
deltaT
)
})
const
scalePower
=
this
.
state
.
scalePower
const
width
=
this
.
container
.
current
.
offsetWidth
const
height
=
this
.
props
.
height
const
margin
=
Math
.
round
(
0.15
*
height
)
const
x
=
scaleBand
().
rangeRound
([
margin
,
width
]).
padding
(
0.1
)
const
y
=
scalePow
().
range
([
height
-
margin
,
margin
]).
exponent
(
scalePower
)
const
width
=
containerRef
.
current
.
offsetWidth
const
height
=
250
const
marginRight
=
32
const
marginTop
=
0
const
marginBottom
=
16
const
y
=
scalePow
().
range
([
height
-
marginBottom
,
marginTop
]).
exponent
(
scale
)
const
max
=
d3
.
max
(
data
,
d
=>
d
.
value
)
||
0
x
.
domain
(
data
.
map
(
d
=>
d
.
name
))
y
.
domain
([
0
,
max
])
let
svg
=
d3
.
select
(
this
.
svgEl
.
current
)
svg
.
attr
(
'
width
'
,
width
)
svg
.
attr
(
'
height
'
,
height
)
const
x
=
scaleTime
()
.
domain
([
Dates
.
addSeconds
(
fromTime
,
-
interval
),
Dates
.
addSeconds
(
untilTime
,
interval
)])
.
rangeRound
([
marginRight
,
width
])
const
container
=
d3
.
select
(
containerRef
.
current
)
const
tooltip
=
container
.
select
(
'
.
'
+
classes
.
tooltip
)
.
style
(
'
opacity
'
,
0
)
const
tooltipContent
=
container
.
select
(
'
.
'
+
classes
.
tooltipContent
)
const
svg
=
container
.
select
(
'
svg
'
)
.
attr
(
'
width
'
,
width
)
.
attr
(
'
height
'
,
height
)
const
xAxis
=
d3
.
axisBottom
(
x
)
svg
.
select
(
'
.xaxis
'
).
remove
()
svg
.
append
(
'
g
'
)
.
attr
(
'
transform
'
,
`translate(0,
${
height
-
margin
}
)`
)
.
attr
(
'
transform
'
,
`translate(0,
${
height
-
margin
Bottom
}
)`
)
.
attr
(
'
class
'
,
'
xaxis
'
)
.
call
(
xAxis
)
...
...
@@ -198,19 +128,19 @@ class UploadsHistogramUnstyled extends React.Component {
const
yAxis
=
d3
.
axisLeft
(
y
).
ticks
(
Math
.
min
(
max
,
5
),
'
.0s
'
)
svg
.
select
(
'
.yaxis
'
).
remove
()
svg
.
append
(
'
g
'
)
.
attr
(
'
transform
'
,
`translate(
${
margin
}
, 0)`
)
.
attr
(
'
transform
'
,
`translate(
${
margin
Right
}
, 0)`
)
.
attr
(
'
class
'
,
'
yaxis
'
)
.
call
(
yAxis
)
const
{
label
,
shortLabel
}
=
this
.
props
.
metricsDefinitions
[
this
.
props
.
metric
]
svg
.
select
(
'
.ylabel
'
).
remove
()
svg
.
append
(
'
text
'
)
.
attr
(
'
class
'
,
'
ylabel
'
)
.
attr
(
'
x
'
,
0
)
.
attr
(
'
y
'
,
1
0
)
.
attr
(
'
dy
'
,
'
1em
'
)
.
attr
(
'
font-size
'
,
'
12px
'
)
.
text
(
`
${
shortLabel
||
label
}
`
)
const
{
label
,
shortLabel
}
=
domain
.
searchMetrics
[
metric
]
//
svg.select('.ylabel').remove()
//
svg.append('text')
//
.attr('class', 'ylabel')
//
.attr('x', 0)
//
.attr('y', 0)
//
.attr('dy', '1em')
//
.attr('font-size', '12px')
//
.text(`${shortLabel || label}`)
let
withData
=
svg
.
selectAll
(
'
.bar
'
).
remove
().
exit
()
...
...
@@ -222,154 +152,127 @@ class UploadsHistogramUnstyled extends React.Component {
item
.
append
(
'
rect
'
)
.
attr
(
'
class
'
,
'
bar
'
)
.
attr
(
'
x
'
,
d
=>
x
(
d
.
na
me
))
.
attr
(
'
x
'
,
d
=>
x
(
d
.
ti
me
)
+
1
)
.
attr
(
'
y
'
,
d
=>
y
(
d
.
value
))
.
attr
(
'
width
'
,
x
.
bandwidth
()
)
.
attr
(
'
width
'
,
d
=>
x
(
Dates
.
addSeconds
(
d
.
time
,
interval
))
-
x
(
d
.
time
)
-
2
)
.
attr
(
'
height
'
,
d
=>
y
(
0
)
-
y
(
d
.
value
))
.
style
(
'
fill
'
,
nomadSecondaryColor
.
light
)
item
.
style
(
'
cursor
'
,
'
pointer
'
)
.
on
(
'
click
'
,
d
=>
this
.
handleItemClicked
(
d
,
deltaT
))
svg
.
select
(
'
.tooltip
'
).
remove
()
let
tooltip
=
svg
.
append
(
'
g
'
)
tooltip
.
attr
(
'
class
'
,
'
tooltip
'
)
.
style
(
'
visibility
'
,
'
hidden
'
)
tooltip
.
append
(
'
rect
'
)
.
attr
(
'
x
'
,
0
)
.
attr
(
'
rx
'
,
6
)
.
attr
(
'
ry
'
,
6
)
.
attr
(
'
width
'
,
100
)
.
attr
(
'
height
'
,
40
)
.
attr
(
'
fill
'
,
'
grey
'
)
.
style
(
'
opacity
'
,
1.0
)
let
tooltipText
=
tooltip
.
append
(
'
text
'
)
.
attr
(
'
dy
'
,
'
1.2em
'
)
.
attr
(
'
font-family
'
,
'
Arial, Helvetica, sans-serif
'
)
.
attr
(
'
font-size
'
,
'
10px
'
)
.
attr
(
'
fill
'
,
'
white
'
)
.
style
(
'
text-anchor
'
,
'
middle
'
)
if
(
clickable
)
{
item
.
style
(
'
cursor
'
,
'
pointer
'
)
.
on
(
'
click
'
,
handleItemClicked
)
}
item
.
on
(
'
mouseover
'
,
function
(
d
)
{
tooltip
.
style
(
'
visibility
'
,
'
visible
'
)
const
date
=
new
Date
(
d
.
time
)
const
value
=
`
${
date
.
toLocaleDateString
()}
\n
${
date
.
toLocaleTimeString
()}
${
d
.
value
}
${
shortLabel
||
label
}
`
tooltipText
.
selectAll
(
'
tspan
'
)
.
data
((
value
).
split
(
/
\n
/
)).
join
(
'
tspan
'
)
.
attr
(
'
x
'
,
50
)
.
attr
(
'
y
'
,
(
d
,
i
)
=>
`
${
i
*
1.2
}
em`
)
.
text
(
d
=>
d
)
const
xPosition
=
x
(
d
.
name
)
+
20
const
yPosition
=
y
(
d
.
value
)
-
20
tooltip
.
attr
(
'
transform
'
,
`translate(
${
xPosition
}
,
${
yPosition
}
)`
)
d3
.
select
(
this
).
select
(
'
.background
'
)
.
style
(
'
opacity
'
,
0.08
)
if
(
tooltips
)
{
tooltip
.
transition
()
.
duration
(
200
)
.
style
(
'
opacity
'
,
1
)
tooltip
.
style
(
'
left
'
,
x
(
d
.
time
)
+
'
px
'
)
.
style
(
'
bottom
'
,
'
24px
'
)
tooltipContent
.
html
(
`
${
d
.
time
.
toLocaleDateString
()}
-
${
Dates
.
addSeconds
(
d
.
time
,
interval
).
toLocaleDateString
()}
with
${
d
.
value
.
toLocaleString
()}
${
shortLabel
||
label
}
`
)
}
})
.
on
(
'
mouseout
'
,
()
=>
tooltip
.
style
(
'
visibility
'
,
'
hidden
'
))
}
.
on
(
'
mouseout
'
,
function
(
d
)
{
d3
.
select
(
this
).
select
(
'
.background
'
)
.
style
(
'
opacity
'
,
0
)
if
(
tooltips
)
{
tooltip
.
transition
()
.
duration
(
200
)
.
style
(
'
opacity
'
,
0
)
}
})
})
render
()
{
return
(
<
div
>
<
Grid
container
justify
=
'
space-between
'
alignItems
=
'
flex-end
'
>
<
Grid
item
xs
=
{
2
}
>
<
Select
margin
=
'
none
'
id
=
'
scales
'
value
=
{
this
.
state
.
scalePower
}
onChange
=
{(
event
)
=>
this
.
setState
({
scalePower
:
event
.
target
.
value
})}
label
=
'
scale
'
>
{
this
.
scales
.
map
(
item
=>
(
<
MenuItem
value
=
{
item
.
value
}
key
=
{
item
.
label
}
>
{
item
.
label
}
<
/MenuItem>
))
}
<
/Select
>
<
/Grid
>
<
Grid
item
xs
=
{
2
}
>
const
handleDatePickerChange
=
useCallback
((
event
,
key
)
=>
{
try
{
const
date
=
new
Date
(
event
.
target
.
value
).
getTime
()
if
(
date
<
Dates
.
JSDate
(
Dates
.
dateHistogramStartDate
).
getTime
())
{
return
}
if
(
date
>
new
Date
().
getTime
())
{
return
}
const
value
=
Dates
.
APIDate
(
new
Date
(
event
.
target
.
value
))
setQuery
({...
query
,
[
key
]:
value
})
}
catch
(
error
)
{
}
})
return
<
Card
classes
=
{{
root
:
classes
.
root
}}
>
<
CardHeader
title
=
{
title
}
titleTypographyProps
=
{{
variant
:
'
body1
'
}}
action
=
{(
<
Grid
container
alignItems
=
'
flex-end
'
spacing
=
{
2
}
>
<
Grid
item
>
<
TextField
i
d
=
'
from
_t
ime
'
i
nputRef
=
{
from
T
ime
FieldRef
}
label
=
"
from time
"
type
=
"
date
"
onChange
=
{(
event
)
=>
this
.
handleTimeChange
(
event
.
target
.
value
,
'
from_time
'
,
'
state
'
)}
defaultValue
=
{
Dates
.
FormDate
(
query
.
from_time
||
Dates
.
dateHistogramStartDate
)}
onChange
=
{
event
=>
handleDatePickerChange
(
event
,
'
from_time
'
)}
InputLabelProps
=
{{
shrink
:
true
}}
/
>
<
/Grid
>
<
Grid
item
xs
=
{
2
}
>
<
Grid
item
>
<
TextField
i
d
=
'
until
_t
ime
'
i
nputRef
=
{
until
T
ime
FieldRef
}
label
=
"
until time
"
type
=
"
date
"
onChange
=
{(
event
)
=>
this
.
handleTimeChange
(
event
.
target
.
value
,
'
until_time
'
,
'
state
'
)}
defaultValue
=
{
Dates
.
FormDate
(
query
.
until_time
||
new
Date
())}
onChange
=
{
event
=>
handleDatePickerChange
(
event
,
'
until_time
'
)}
InputLabelProps
=
{{
shrink
:
true
}}
/
>
<
/Grid
>
<
Grid
item
xs
=
{
2
}
>
<
Button
variant
=
'
outlined
'
color
=
'
default
'
onClick
=
{()
=>
this
.
handleQueryChange
()}
>
Refresh
<
RefreshIcon
/>
<
/Button
>