1
+ import request from "@/utils/request" ;
2
+ import { cloneDeep } from "lodash" ;
3
+ import { useEffect , useRef , useState } from "react" ;
4
+ import { formatMessage } from "umi/locale" ;
5
+ import styles from "./Metrics.scss" ;
6
+ import { Empty , Icon , message , Spin , Tooltip } from "antd" ;
7
+ import {
8
+ Axis ,
9
+ Chart ,
10
+ CurveType ,
11
+ LineSeries ,
12
+ BarSeries ,
13
+ niceTimeFormatByDay ,
14
+ Position ,
15
+ ScaleType ,
16
+ Settings ,
17
+ timeFormatter ,
18
+ } from "@elastic/charts" ;
19
+ import { formatter , getFormatter , getNumFormatter } from "@/utils/format" ;
20
+ import { CopyToClipboard } from "react-copy-to-clipboard" ;
21
+ import moment from "moment" ;
22
+ import { ESPrefix } from "@/services/common" ;
23
+
24
+ export default ( props ) => {
25
+
26
+ const {
27
+ timezone,
28
+ timeRange,
29
+ handleTimeChange,
30
+ fetchUrl,
31
+ metricKey,
32
+ title,
33
+ queryParams,
34
+ className,
35
+ style,
36
+ formatMetric
37
+ } = props ;
38
+
39
+ const [ loading , setLoading ] = useState ( false )
40
+
41
+ const [ metric , setMetric ] = useState ( )
42
+
43
+ const [ isInView , setIsInView ] = useState ( false ) ;
44
+
45
+ const observerRef = useRef ( { isInView : false } )
46
+
47
+ const containerRef = useRef ( null )
48
+
49
+ const firstFetchRef = useRef ( true )
50
+
51
+ const fetchData = async ( queryParams , fetchUrl , metricKey ) => {
52
+ if ( ! observerRef . current . isInView ) return ;
53
+ if ( firstFetchRef . current ) {
54
+ setLoading ( true )
55
+ }
56
+ const res = await request ( fetchUrl , {
57
+ method : 'GET' ,
58
+ queryParams : {
59
+ ...queryParams ,
60
+ key : metricKey
61
+ } ,
62
+ } )
63
+ if ( res && ! res . error ) {
64
+ const { metrics = { } } = res || { } ;
65
+ const metric = metrics [ metricKey ]
66
+ setMetric ( formatMetric ? formatMetric ( metric ) : metric ) ;
67
+ }
68
+ if ( firstFetchRef . current ) {
69
+ setLoading ( false )
70
+ firstFetchRef . current = false
71
+ }
72
+ }
73
+
74
+ useEffect ( ( ) => {
75
+ observerRef . current . deps = cloneDeep ( [ queryParams , fetchUrl , metricKey ] )
76
+ fetchData ( queryParams , fetchUrl , metricKey )
77
+ } , [ JSON . stringify ( queryParams ) , fetchUrl , metricKey ] )
78
+
79
+ useEffect ( ( ) => {
80
+ const observer = new IntersectionObserver (
81
+ entries => {
82
+ entries . forEach ( entry => {
83
+ if ( entry . isIntersecting ) {
84
+ observerRef . current . isInView = true
85
+ if ( JSON . stringify ( observerRef . current . deps ) !== JSON . stringify ( observerRef . current . lastDeps ) ) {
86
+ observerRef . current . lastDeps = cloneDeep ( observerRef . current . deps )
87
+ fetchData ( ...observerRef . current . deps )
88
+ }
89
+ } else {
90
+ observerRef . current . isInView = false
91
+ }
92
+ } ) ;
93
+ } ,
94
+ {
95
+ root : null ,
96
+ threshold : 0 ,
97
+ }
98
+ ) ;
99
+
100
+ if ( containerRef . current ) {
101
+ observer . observe ( containerRef . current ) ;
102
+ }
103
+
104
+ return ( ) => {
105
+ if ( containerRef . current ) {
106
+ observer . unobserve ( containerRef . current ) ;
107
+ }
108
+ } ;
109
+ } , [ isInView ] ) ;
110
+
111
+ const chartRef = useRef ( ) ;
112
+
113
+ const pointerUpdate = ( event ) => {
114
+ if ( chartRef . current ) {
115
+ chartRef . current . dispatchExternalPointerEvent ( event ) ;
116
+ }
117
+ } ;
118
+
119
+ const handleChartBrush = ( { x } ) => {
120
+ if ( ! x ) {
121
+ return ;
122
+ }
123
+ let [ from , to ] = x ;
124
+ if ( typeof handleTimeChange == "function" ) {
125
+ if ( to - from < 20 * 1000 ) {
126
+ from -= 10 * 1000 ;
127
+ to += 10 * 1000 ;
128
+ }
129
+ handleTimeChange ( {
130
+ start : moment ( from ) . toISOString ( ) ,
131
+ end : moment ( to ) . toISOString ( ) ,
132
+ } ) ;
133
+ }
134
+ } ;
135
+
136
+ const axis = metric ?. axis || [ ] ;
137
+ const lines = metric ?. lines || [ ] ;
138
+ let disableHeaderFormat = false ;
139
+ const chartTitle = { title } ;
140
+ if ( metricKey == "cluster_health" ) {
141
+ chartTitle . units = "%" ;
142
+ } else {
143
+ if ( lines [ 0 ] ?. metric ) {
144
+ if ( lines [ 0 ] . metric . formatType . toLowerCase == "bytes" ) {
145
+ chartTitle . units = lines [ 0 ] . metric . formatType ;
146
+ } else {
147
+ chartTitle . units = lines [ 0 ] . metric . units ;
148
+ }
149
+ }
150
+ }
151
+
152
+ return (
153
+ < div key = { metricKey } ref = { containerRef } className = { className } style = { style } >
154
+ < Spin spinning = { loading } >
155
+ < div className = { styles . vizChartItemTitle } >
156
+ < span >
157
+ { chartTitle . title }
158
+ { chartTitle . units ? `(${ chartTitle . units } )` : "" }
159
+ </ span >
160
+ {
161
+ metric ?. request && (
162
+ < span >
163
+ < CopyToClipboard text = { metric . request } >
164
+ < Tooltip title = { formatMessage ( { id : "cluster.metrics.dsl.copy" } ) } >
165
+ < Icon
166
+ className = { styles . copy }
167
+ type = "copy"
168
+ onClick = { ( ) => message . success ( formatMessage ( { id : "cluster.metrics.dsl.copy.success" } ) ) }
169
+ />
170
+ </ Tooltip >
171
+ </ CopyToClipboard >
172
+ </ span >
173
+ )
174
+ }
175
+ </ div >
176
+ {
177
+ lines . every ( ( item ) => ! item . data || item . data . length === 0 ) ? (
178
+ < Empty style = { { height : 200 , margin : 0 , paddingTop : 64 } } image = { Empty . PRESENTED_IMAGE_SIMPLE } />
179
+ ) : (
180
+ < Chart
181
+ size = { [ , 200 ] }
182
+ className = { styles . vizChartItem }
183
+ ref = { chartRef }
184
+ >
185
+ < Settings
186
+ pointerUpdateDebounce = { 0 }
187
+ pointerUpdateTrigger = "x"
188
+ // externalPointerEvents={{
189
+ // tooltip: { visible: true },
190
+ // }}
191
+ onPointerUpdate = { pointerUpdate }
192
+ // theme={theme}
193
+ showLegend
194
+ legendPosition = { Position . Bottom }
195
+ onBrushEnd = { handleChartBrush }
196
+ tooltip = { {
197
+ headerFormatter : disableHeaderFormat
198
+ ? undefined
199
+ : ( { value } ) =>
200
+ `${ formatter . full_dates ( value ) } ` ,
201
+ } }
202
+ debug = { false }
203
+ />
204
+ < Axis
205
+ id = { `${ metricKey } -bottom` }
206
+ position = { Position . Bottom }
207
+ showOverlappingTicks
208
+ labelFormat = { timeRange . timeFormatter }
209
+ tickFormat = { timeRange . timeFormatter }
210
+ />
211
+ { metricKey == "cluster_health" ? (
212
+ < Axis
213
+ id = "cluster_health"
214
+ position = { Position . Left }
215
+ tickFormat = { ( d ) => Number ( d ) . toFixed ( 0 ) + "%" }
216
+ />
217
+ ) : null }
218
+
219
+ { axis . map ( ( item ) => {
220
+ return (
221
+ < Axis
222
+ key = { metricKey + "-" + item . id }
223
+ id = { metricKey + "-" + item . id }
224
+ showGridLines = { item . showGridLines }
225
+ groupId = { item . group }
226
+ position = { item . position }
227
+ ticks = { item . ticks }
228
+ labelFormat = { getFormatter (
229
+ item . formatType ,
230
+ item . labelFormat
231
+ ) }
232
+ tickFormat = { getFormatter (
233
+ item . formatType ,
234
+ item . tickFormat
235
+ ) }
236
+ />
237
+ ) ;
238
+ } ) }
239
+
240
+ { lines . map ( ( item ) => {
241
+ if ( item . type == "Bar" ) {
242
+ return (
243
+ < BarSeries
244
+ key = { item . metric . label }
245
+ xScaleType = { ScaleType . Time }
246
+ yScaleType = { ScaleType . Linear }
247
+ xAccessor = "x"
248
+ yAccessors = { [ "y" ] }
249
+ stackAccessors = { [ "x" ] }
250
+ splitSeriesAccessors = { [ "g" ] }
251
+ data = { item . data }
252
+ color = { ( { specId, yAccessor, splitAccessors } ) => {
253
+ const g = splitAccessors . get ( "g" ) ;
254
+ if (
255
+ yAccessor === "y"
256
+
257
+ ) {
258
+ if ( [ "red" , "yellow" , "green" ] . includes ( g ) ) {
259
+ return g ;
260
+ }
261
+ if ( g == "online" || g == "available" ) {
262
+ return "green" ;
263
+ }
264
+ if ( g == "offline" || g == "unavailable" || g == "N/A" ) {
265
+ return "gray" ;
266
+ }
267
+
268
+ }
269
+ return null ;
270
+ } }
271
+ />
272
+ ) ;
273
+ }
274
+ return (
275
+ < LineSeries
276
+ key = { item . metric . label }
277
+ id = { item . metric . label }
278
+ groupId = { item . metric . group }
279
+ timeZone = { timezone }
280
+ color = { item . color }
281
+ xScaleType = { ScaleType . Time }
282
+ yScaleType = { ScaleType . Linear }
283
+ xAccessor = { 0 }
284
+ tickFormat = { getFormatter (
285
+ item . metric . formatType ,
286
+ item . metric . tickFormat ,
287
+ item . metric . units
288
+ ) }
289
+ yAccessors = { [ 1 ] }
290
+ data = { item . data }
291
+ curve = { CurveType . CURVE_MONOTONE_X }
292
+ />
293
+ ) ;
294
+ } ) }
295
+ </ Chart >
296
+ )
297
+ }
298
+ </ Spin >
299
+ </ div >
300
+ ) ;
301
+ }
0 commit comments