1
1
import { Paper , Skeleton , Stack , Text } from '@mantine/core' ;
2
2
import classes from '../styles/Correlation.module.css' ;
3
- import { useQueryResult } from '@/hooks/useQueryResult' ;
3
+ import { useGraphData } from '@/hooks/useQueryResult' ;
4
4
import { useCallback , useEffect , useMemo , useState } from 'react' ;
5
5
import dayjs from 'dayjs' ;
6
6
import { AreaChart } from '@mantine/charts' ;
@@ -9,20 +9,13 @@ import { appStoreReducers, useAppStore } from '@/layouts/MainLayout/providers/Ap
9
9
import { LogsResponseWithHeaders } from '@/@types/parseable/api/query' ;
10
10
import _ from 'lodash' ;
11
11
import timeRangeUtils from '@/utils/timeRangeUtils' ;
12
- import { filterStoreReducers , useFilterStore } from '@/pages/Stream/providers/FilterProvider' ;
13
12
import { useCorrelationStore } from '../providers/CorrelationProvider' ;
14
13
15
- const { parseQuery } = filterStoreReducers ;
16
14
const { makeTimeRangeLabel } = timeRangeUtils ;
17
15
const { setTimeRange } = appStoreReducers ;
18
16
19
17
type CompactInterval = 'minute' | 'day' | 'hour' | 'quarter-hour' | 'half-hour' | 'month' ;
20
18
21
- function removeOffsetFromQuery ( query : string ) : string {
22
- const offsetRegex = / \s O F F S E T \s + \d + / i;
23
- return query . replace ( offsetRegex , '' ) ;
24
- }
25
-
26
19
const getCompactType = ( interval : number ) : CompactInterval => {
27
20
const totalMinutes = interval / ( 1000 * 60 ) ;
28
21
if ( totalMinutes <= 60 ) {
@@ -44,74 +37,6 @@ const getCompactType = (interval: number): CompactInterval => {
44
37
}
45
38
} ;
46
39
47
- const getStartOfTs = ( time : Date , compactType : CompactInterval ) : Date => {
48
- if ( compactType === 'minute' ) {
49
- return time ;
50
- } else if ( compactType === 'hour' ) {
51
- return new Date ( time . getFullYear ( ) , time . getMonth ( ) , time . getDate ( ) , time . getHours ( ) ) ;
52
- } else if ( compactType === 'quarter-hour' ) {
53
- const roundOff = 1000 * 60 * 15 ;
54
- return new Date ( Math . floor ( time . getTime ( ) / roundOff ) * roundOff ) ;
55
- } else if ( compactType === 'half-hour' ) {
56
- const roundOff = 1000 * 60 * 30 ;
57
- return new Date ( Math . floor ( time . getTime ( ) / roundOff ) * roundOff ) ;
58
- } else if ( compactType === 'day' ) {
59
- return new Date ( time . getFullYear ( ) , time . getMonth ( ) , time . getDate ( ) ) ;
60
- } else {
61
- return new Date ( time . getFullYear ( ) , time . getMonth ( ) ) ;
62
- }
63
- } ;
64
-
65
- const getEndOfTs = ( time : Date , compactType : CompactInterval ) : Date => {
66
- if ( compactType === 'minute' ) {
67
- return time ;
68
- } else if ( compactType === 'hour' ) {
69
- return new Date ( time . getFullYear ( ) , time . getMonth ( ) , time . getDate ( ) , time . getHours ( ) + 1 ) ;
70
- } else if ( compactType === 'quarter-hour' ) {
71
- const roundOff = 1000 * 60 * 15 ;
72
- return new Date ( Math . round ( time . getTime ( ) / roundOff ) * roundOff ) ;
73
- } else if ( compactType === 'half-hour' ) {
74
- const roundOff = 1000 * 60 * 30 ;
75
- return new Date ( Math . round ( time . getTime ( ) / roundOff ) * roundOff ) ;
76
- } else if ( compactType === 'day' ) {
77
- return new Date ( time . getFullYear ( ) , time . getMonth ( ) , time . getDate ( ) + 1 ) ;
78
- } else {
79
- return new Date ( time . getFullYear ( ) , time . getMonth ( ) + 1 ) ;
80
- }
81
- } ;
82
-
83
- const getModifiedTimeRange = (
84
- startTime : Date ,
85
- endTime : Date ,
86
- interval : number ,
87
- ) : { modifiedStartTime : Date ; modifiedEndTime : Date ; compactType : CompactInterval } => {
88
- const compactType = getCompactType ( interval ) ;
89
- const modifiedStartTime = getStartOfTs ( startTime , compactType ) ;
90
- const modifiedEndTime = getEndOfTs ( endTime , compactType ) ;
91
- return { modifiedEndTime, modifiedStartTime, compactType } ;
92
- } ;
93
-
94
- const compactTypeIntervalMap = {
95
- minute : '1 minute' ,
96
- hour : '1 hour' ,
97
- day : '24 hour' ,
98
- 'quarter-hour' : '15 minute' ,
99
- 'half-hour' : '30 minute' ,
100
- month : '1 month' ,
101
- } ;
102
-
103
- const generateCountQuery = (
104
- streamName : string ,
105
- startTime : Date ,
106
- endTime : Date ,
107
- compactType : CompactInterval ,
108
- whereClause : string ,
109
- ) => {
110
- const range = compactTypeIntervalMap [ compactType ] ;
111
- /* eslint-disable no-useless-escape */
112
- return `SELECT DATE_BIN('${ range } ', p_timestamp, '${ startTime . toISOString ( ) } ') AS date_bin_timestamp, COUNT(*) AS log_count FROM \"${ streamName } \" WHERE p_timestamp BETWEEN '${ startTime . toISOString ( ) } ' AND '${ endTime . toISOString ( ) } ' AND ${ whereClause } GROUP BY date_bin_timestamp ORDER BY date_bin_timestamp` ;
113
- } ;
114
-
115
40
const NoDataView = ( props : { isError : boolean } ) => {
116
41
return (
117
42
< Stack style = { { width : '100%' , height : '100%' , alignItems : 'center' , justifyContent : 'center' } } >
@@ -136,38 +61,6 @@ const calcAverage = (data: LogsResponseWithHeaders | undefined) => {
136
61
return parseInt ( Math . abs ( total / records . length ) . toFixed ( 0 ) ) ;
137
62
} ;
138
63
139
- const getAllIntervals = ( start : Date , end : Date , compactType : CompactInterval ) : Date [ ] => {
140
- const result = [ ] ;
141
- let currentDate = new Date ( start ) ;
142
-
143
- while ( currentDate <= end ) {
144
- result . push ( new Date ( currentDate ) ) ;
145
- currentDate = incrementDateByCompactType ( currentDate , compactType ) ;
146
- }
147
-
148
- return result ;
149
- } ;
150
-
151
- const incrementDateByCompactType = ( date : Date , type : CompactInterval ) : Date => {
152
- const tempDate = new Date ( date ) ;
153
- if ( type === 'minute' ) {
154
- tempDate . setMinutes ( tempDate . getMinutes ( ) + 1 ) ;
155
- } else if ( type === 'hour' ) {
156
- tempDate . setHours ( tempDate . getHours ( ) + 1 ) ;
157
- } else if ( type === 'day' ) {
158
- tempDate . setDate ( tempDate . getDate ( ) + 1 ) ;
159
- } else if ( type === 'quarter-hour' ) {
160
- tempDate . setMinutes ( tempDate . getMinutes ( ) + 15 ) ;
161
- } else if ( type === 'half-hour' ) {
162
- tempDate . setMinutes ( tempDate . getMinutes ( ) + 30 ) ;
163
- } else if ( type === 'month' ) {
164
- tempDate . setMonth ( tempDate . getMonth ( ) + 1 ) ;
165
- } else {
166
- tempDate ;
167
- }
168
- return new Date ( tempDate ) ;
169
- } ;
170
-
171
64
type GraphTickItem = {
172
65
events : number ;
173
66
minute : Date ;
@@ -243,6 +136,11 @@ function ChartTooltip({ payload, series }: ChartTooltipProps) {
243
136
) ;
244
137
}
245
138
139
+ type LogRecord = {
140
+ counts_timestamp : string ;
141
+ log_count : number ;
142
+ } ;
143
+
246
144
// date_bin removes tz info
247
145
// filling data with empty values where there is no rec
248
146
const parseGraphData = (
@@ -256,39 +154,53 @@ const parseGraphData = (
256
154
const firstResponse = dataSets [ 0 ] ?. records || [ ] ;
257
155
const secondResponse = dataSets [ 1 ] ?. records || [ ] ;
258
156
259
- const { modifiedEndTime, modifiedStartTime, compactType } = getModifiedTimeRange ( startTime , endTime , interval ) ;
260
- const allTimestamps = getAllIntervals ( modifiedStartTime , modifiedEndTime , compactType ) ;
157
+ const compactType = getCompactType ( interval ) ;
158
+ const ticksCount = interval < 10 * 60 * 1000 ? interval / ( 60 * 1000 ) : interval < 60 * 60 * 1000 ? 10 : 60 ;
159
+ const intervalDuration = ( endTime . getTime ( ) - startTime . getTime ( ) ) / ticksCount ;
160
+
161
+ const allTimestamps = Array . from (
162
+ { length : ticksCount } ,
163
+ ( _ , index ) => new Date ( startTime . getTime ( ) + index * intervalDuration ) ,
164
+ ) ;
261
165
262
166
const hasSecondDataset = dataSets [ 1 ] !== undefined ;
263
167
168
+ const isValidRecord = ( record : any ) : record is LogRecord => {
169
+ return typeof record . counts_timestamp === 'string' && typeof record . log_count === 'number' ;
170
+ } ;
171
+
264
172
const secondResponseMap =
265
173
secondResponse . length > 0
266
174
? new Map (
267
- secondResponse . map ( ( entry ) => [ new Date ( `${ entry . date_bin_timestamp } Z` ) . toISOString ( ) , entry . log_count ] ) ,
175
+ secondResponse
176
+ . filter ( ( entry ) => isValidRecord ( entry ) )
177
+ . map ( ( entry ) => {
178
+ const timestamp = entry . counts_timestamp ;
179
+ if ( timestamp != null ) {
180
+ return [ new Date ( timestamp ) . getTime ( ) , entry . log_count ] ;
181
+ }
182
+ return null ;
183
+ } )
184
+ . filter ( ( entry ) : entry is [ number , number ] => entry !== null ) ,
268
185
)
269
186
: new Map ( ) ;
270
- const calculateTimeRange = ( timestamp : Date | string ) => {
271
- const startTime = dayjs ( timestamp ) ;
272
- const endTimeByCompactType = incrementDateByCompactType ( startTime . toDate ( ) , compactType ) ;
273
- const endTime = dayjs ( endTimeByCompactType ) ;
274
- return { startTime, endTime } ;
275
- } ;
187
+
276
188
const combinedData = allTimestamps . map ( ( ts ) => {
277
189
const firstRecord = firstResponse . find ( ( record ) => {
278
- const recordTimestamp = new Date ( `${ record . date_bin_timestamp } Z` ) . toISOString ( ) ;
279
- const tsISO = ts . toISOString ( ) ;
190
+ if ( ! isValidRecord ( record ) ) return false ;
191
+ const recordTimestamp = new Date ( record . counts_timestamp ) . getTime ( ) ;
192
+ const tsISO = ts . getTime ( ) ;
280
193
return recordTimestamp === tsISO ;
281
194
} ) ;
282
195
283
196
const secondCount = secondResponseMap ?. get ( ts . toISOString ( ) ) ?? 0 ;
284
- const { startTime, endTime } = calculateTimeRange ( ts ) ;
285
197
286
198
const defaultOpts : Record < string , any > = {
287
199
stream : firstRecord ?. log_count || 0 ,
288
200
minute : ts ,
289
201
compactType,
290
- startTime,
291
- endTime,
202
+ startTime : dayjs ( ts ) ,
203
+ endTime : dayjs ( new Date ( ts . getTime ( ) + intervalDuration ) ) ,
292
204
} ;
293
205
294
206
if ( hasSecondDataset ) {
@@ -302,9 +214,8 @@ const parseGraphData = (
302
214
} ;
303
215
304
216
const MultiEventTimeLineGraph = ( ) => {
305
- const { fetchQueryMutation } = useQueryResult ( ) ;
217
+ const { fetchGraphDataMutation } = useGraphData ( ) ;
306
218
const [ fields ] = useCorrelationStore ( ( store ) => store . fields ) ;
307
- const [ appliedQuery ] = useFilterStore ( ( store ) => store . appliedQuery ) ;
308
219
const [ streamData ] = useCorrelationStore ( ( store ) => store . streamData ) ;
309
220
const [ timeRange ] = useAppStore ( ( store ) => store . timeRange ) ;
310
221
const [ multipleStreamData , setMultipleStreamData ] = useState < { [ key : string ] : any } > ( { } ) ;
@@ -334,30 +245,23 @@ const MultiEventTimeLineGraph = () => {
334
245
335
246
const streamNames = Object . keys ( fields ) ;
336
247
const streamsToFetch = streamNames . filter ( ( streamName ) => ! Object . keys ( streamData ) . includes ( streamName ) ) ;
337
- const queries = streamsToFetch . map ( ( streamKey ) => {
338
- const { modifiedEndTime, modifiedStartTime, compactType } = getModifiedTimeRange ( startTime , endTime , interval ) ;
248
+ const totalMinutes = interval / ( 1000 * 60 ) ;
249
+ const numBins = Math . trunc ( totalMinutes < 10 ? totalMinutes : totalMinutes < 60 ? 10 : 60 ) ;
250
+ const eventTimeLineGraphOpts = streamsToFetch . map ( ( streamKey ) => {
339
251
const logsQuery = {
340
- startTime : modifiedStartTime ,
341
- endTime : modifiedEndTime ,
342
- access : [ ] ,
343
- } ;
344
- const whereClause = parseQuery ( appliedQuery , streamKey ) . where ;
345
- const query = generateCountQuery ( streamKey , modifiedStartTime , modifiedEndTime , compactType , whereClause ) ;
346
- const graphQuery = removeOffsetFromQuery ( query ) ;
347
-
348
- return {
349
- queryEngine : 'Parseable' ,
350
- logsQuery,
351
- query : graphQuery ,
352
- streamKey,
252
+ stream : streamKey ,
253
+ startTime : dayjs ( startTime ) . toISOString ( ) ,
254
+ endTime : dayjs ( endTime ) . add ( 1 , 'minute' ) . toISOString ( ) ,
255
+ numBins,
353
256
} ;
257
+ return logsQuery ;
354
258
} ) ;
355
- Promise . all ( queries . map ( ( queryData : any ) => fetchQueryMutation . mutateAsync ( queryData ) ) )
259
+ Promise . all ( eventTimeLineGraphOpts . map ( ( queryData : any ) => fetchGraphDataMutation . mutateAsync ( queryData ) ) )
356
260
. then ( ( results ) => {
357
261
setMultipleStreamData ( ( prevData : any ) => {
358
262
const newData = { ...prevData } ;
359
263
results . forEach ( ( result , index ) => {
360
- newData [ queries [ index ] . streamKey ] = result ;
264
+ newData [ eventTimeLineGraphOpts [ index ] . stream ] = result ;
361
265
} ) ;
362
266
return newData ;
363
267
} ) ;
@@ -367,8 +271,8 @@ const MultiEventTimeLineGraph = () => {
367
271
} ) ;
368
272
} , [ fields , timeRange ] ) ;
369
273
370
- const isLoading = fetchQueryMutation . isLoading ;
371
- const avgEventCount = useMemo ( ( ) => calcAverage ( fetchQueryMutation ?. data ) , [ fetchQueryMutation ?. data ] ) ;
274
+ const isLoading = fetchGraphDataMutation . isLoading ;
275
+ const avgEventCount = useMemo ( ( ) => calcAverage ( fetchGraphDataMutation ?. data ) , [ fetchGraphDataMutation ?. data ] ) ;
372
276
const graphData = useMemo ( ( ) => {
373
277
if ( ! streamGraphData || streamGraphData . length === 0 || streamGraphData . length !== Object . keys ( fields ) . length )
374
278
return [ ] ;
@@ -394,7 +298,7 @@ const MultiEventTimeLineGraph = () => {
394
298
return (
395
299
< Stack className = { classes . graphContainer } >
396
300
< Skeleton
397
- visible = { fetchQueryMutation . isLoading }
301
+ visible = { fetchGraphDataMutation . isLoading }
398
302
h = "100%"
399
303
w = { isLoading ? '98%' : '100%' }
400
304
style = { isLoading ? { marginLeft : '1.8rem' , alignSelf : 'center' } : ! hasData ? { marginLeft : '1rem' } : { } } >
@@ -433,7 +337,7 @@ const MultiEventTimeLineGraph = () => {
433
337
dotProps = { { strokeWidth : 1 , r : 2.5 } }
434
338
/>
435
339
) : (
436
- < NoDataView isError = { fetchQueryMutation . isError } />
340
+ < NoDataView isError = { fetchGraphDataMutation . isError } />
437
341
) }
438
342
</ Skeleton >
439
343
</ Stack >
0 commit comments