1+ // @ts -nocheck
12// @ts -ignore
23import blessed from 'blessed'
34import contrib from 'blessed-contrib'
@@ -120,6 +121,52 @@ function setupCommand (name: string, description: string, argv: readonly string[
120121 }
121122}
122123
124+ const METRICS = [
125+ 'total_critical_alerts' ,
126+ 'total_high_alerts' ,
127+ 'total_medium_alerts' ,
128+ 'total_low_alerts' ,
129+ 'total_critical_added' ,
130+ 'total_medium_added' ,
131+ 'total_low_added' ,
132+ 'total_high_added' ,
133+ 'total_critical_prevented' ,
134+ 'total_high_prevented' ,
135+ 'total_medium_prevented' ,
136+ 'total_low_prevented'
137+ ]
138+
139+ type AnalyticsData = {
140+ id : number ,
141+ created_at : string
142+ repository_id : string
143+ organization_id : number
144+ repository_name : string
145+ total_critical_alerts : number
146+ total_high_alerts : number
147+ total_medium_alerts : number
148+ total_low_alerts : number
149+ total_critical_added : number
150+ total_high_added : number
151+ total_medium_added : number
152+ total_low_added : number
153+ total_critical_prevented : number
154+ total_high_prevented : number
155+ total_medium_prevented : number
156+ total_low_prevented : number
157+ top_five_alert_types : {
158+ [ key : string ] : number
159+ }
160+ }
161+
162+ type FormattedAnalyticsData = {
163+ [ key : string ] : {
164+ [ key : string ] : number | {
165+ [ key : string ] : number
166+ }
167+ }
168+ }
169+
123170async function fetchOrgAnalyticsData ( time : number , spinner : Ora , apiKey : string , outputJson : boolean ) : Promise < void > {
124171 const socketSdk = await setupSdk ( apiKey )
125172 const result = await handleApiCall ( socketSdk . getOrgAnalytics ( time . toString ( ) ) , 'fetching analytics data' )
@@ -134,15 +181,115 @@ async function fetchOrgAnalyticsData (time: number, spinner: Ora, apiKey: string
134181 return console . log ( 'No analytics data is available for this organization yet.' )
135182 }
136183
137- const data = formatData ( result . data )
184+ const data = formatData ( result . data , 'org' )
185+
186+ console . log ( data )
138187
139188 if ( outputJson ) {
140- return console . log ( data )
189+ return console . log ( result . data )
141190 }
142191
143192 return displayAnalyticsScreen ( data )
144193}
145194
195+ const months = [
196+ 'Jan' ,
197+ 'Feb' ,
198+ 'Mar' ,
199+ 'Apr' ,
200+ 'May' ,
201+ 'Jun' ,
202+ 'Jul' ,
203+ 'Aug' ,
204+ 'Sep' ,
205+ 'Oct' ,
206+ 'Nov' ,
207+ 'Dec'
208+ ]
209+
210+ const formatDate = ( date : string ) => {
211+ return `${ months [ new Date ( date ) . getMonth ( ) ] } ${ new Date ( date ) . getDate ( ) } `
212+ }
213+
214+ const formatData = ( data : AnalyticsData [ ] , scope : string ) => {
215+ let formattedData , sortedTopFivealerts
216+
217+ if ( scope === 'org' ) {
218+ const topFiveAlerts = data . map ( d => d . top_five_alert_types || { } )
219+
220+ const totalTopAlerts = topFiveAlerts . reduce ( ( acc , current : { [ key : string ] : number } ) => {
221+ const alertTypes = Object . keys ( current )
222+ alertTypes . map ( ( type : string ) => {
223+ if ( ! acc [ type ] ) {
224+ acc [ type ] = current [ type ]
225+ } else {
226+ acc [ type ] += current [ type ]
227+ }
228+ return acc
229+ } )
230+ return acc
231+ } , { } as { [ k : string ] : any } )
232+
233+
234+ sortedTopFivealerts = Object . entries ( totalTopAlerts )
235+ . sort ( ( [ , a ] , [ , b ] ) => b - a )
236+ . slice ( 0 , 5 )
237+ . reduce ( ( r , [ k , v ] ) => ( { ...r , [ k ] : v } ) , { } )
238+
239+ const formatData = ( label : string ) => {
240+ return data . reduce ( ( acc , current ) => {
241+ const date : string = formatDate ( current . created_at )
242+ if ( ! acc [ date ] ) {
243+ acc [ date ] = current [ label ]
244+ } else {
245+ acc [ date ] += current [ label ]
246+ }
247+ return acc
248+ } , { } as { [ k : string ] : number } )
249+ }
250+
251+ formattedData = METRICS . reduce ( ( acc , current : string ) => {
252+ acc [ current ] = formatData ( current )
253+ return acc
254+ } , { } as { [ k : string ] : any } )
255+
256+ } else if ( scope === 'repo' ) {
257+
258+ const topAlerts = data . reduce ( ( acc , current ) => {
259+ const alertTypes = Object . keys ( current . top_five_alert_types )
260+ alertTypes . map ( type => {
261+ if ( ! acc [ type ] ) {
262+ acc [ type ] = current . top_five_alert_types [ type ]
263+ } else {
264+ if ( current . top_five_alert_types [ type ] > acc [ type ] ) {
265+ acc [ type ] = current . top_five_alert_types [ type ]
266+ }
267+ }
268+ return acc
269+ } )
270+ return acc
271+ } , { } as { [ key : string ] : number } )
272+
273+ sortedTopFivealerts = Object . entries ( topAlerts )
274+ . sort ( ( [ , a ] : [ string , number ] , [ , b ] : [ string , number ] ) => b - a )
275+ . slice ( 0 , 5 )
276+ . reduce ( ( r , [ k , v ] ) => ( { ...r , [ k ] : v } ) , { } )
277+
278+ formattedData = data . reduce ( ( acc , current ) => {
279+ METRICS . forEach ( ( m : string ) => {
280+ if ( ! acc [ m ] ) {
281+ acc [ m ] = { }
282+ }
283+ acc [ m ] [ formatDate ( current . created_at ) ] = current [ m ]
284+ return acc
285+ } )
286+ return acc
287+ } , { } as { [ k : string ] : any } )
288+ }
289+
290+ return { ...formattedData , top_five_alert_types : sortedTopFivealerts }
291+ }
292+
146293async function fetchRepoAnalyticsData ( repo : string , time : number , spinner : Ora , apiKey : string , outputJson : boolean ) : Promise < void > {
147294 const socketSdk = await setupSdk ( apiKey )
148295 const result = await handleApiCall ( socketSdk . getRepoAnalytics ( repo , time . toString ( ) ) , 'fetching analytics data' )
@@ -156,21 +303,48 @@ async function fetchRepoAnalyticsData (repo: string, time: number, spinner: Ora,
156303 return console . log ( 'No analytics data is available for this organization yet.' )
157304 }
158305
159- const data = formatData ( result . data )
306+ const data = formatData ( result . data , 'repo' )
160307
161308 if ( outputJson ) {
162- return console . log ( data )
309+ return console . log ( result . data )
163310 }
164311
165312 return displayAnalyticsScreen ( data )
166313}
167314
168- const renderLineCharts = ( grid : any , screen : any , title : string , coords : number [ ] , data : FormattedAnalyticsData , label : string ) => {
169- const formattedDates = Object . keys ( data ) . map ( d => `${ new Date ( d ) . getMonth ( ) + 1 } /${ new Date ( d ) . getDate ( ) } ` )
315+ const displayAnalyticsScreen = ( data : FormattedAnalyticsData ) => {
316+ const screen = blessed . screen ( )
317+ // eslint-disable-next-line
318+ const grid = new contrib . grid ( { rows : 5 , cols : 4 , screen} )
170319
171- // @ts -ignore
172- const alertsCounts = Object . values ( data ) . map ( d => d [ label ] )
173-
320+ renderLineCharts ( grid , screen , 'Total critical alerts' , [ 0 , 0 , 1 , 2 ] , data [ 'total_critical_alerts' ] )
321+ renderLineCharts ( grid , screen , 'Total high alerts' , [ 0 , 2 , 1 , 2 ] , data [ 'total_high_alerts' ] )
322+ renderLineCharts ( grid , screen , 'Total critical alerts added to the main branch' , [ 1 , 0 , 1 , 2 ] , data [ 'total_critical_added' ] )
323+ renderLineCharts ( grid , screen , 'Total high alerts added to the main branch' , [ 1 , 2 , 1 , 2 ] , data [ 'total_high_added' ] )
324+ renderLineCharts ( grid , screen , 'Total critical alerts prevented from the main branch' , [ 2 , 0 , 1 , 2 ] , data [ 'total_critical_prevented' ] )
325+ renderLineCharts ( grid , screen , 'Total high alerts prevented from the main branch' , [ 2 , 2 , 1 , 2 ] , data [ 'total_high_prevented' ] )
326+ renderLineCharts ( grid , screen , 'Total medium alerts prevented from the main branch' , [ 3 , 0 , 1 , 2 ] , data [ 'total_medium_prevented' ] )
327+ renderLineCharts ( grid , screen , 'Total low alerts prevented from the main branch' , [ 3 , 2 , 1 , 2 ] , data [ 'total_low_prevented' ] )
328+
329+ const bar = grid . set ( 4 , 0 , 1 , 2 , contrib . bar ,
330+ { label : 'Top 5 alert types'
331+ , barWidth : 10
332+ , barSpacing : 17
333+ , xOffset : 0
334+ , maxHeight : 9 , barBgColor : 'magenta' } )
335+
336+ screen . append ( bar ) //must append before setting data
337+
338+ bar . setData (
339+ { titles : Object . keys ( data . top_five_alert_types )
340+ , data : Object . values ( data . top_five_alert_types ) } )
341+
342+ screen . render ( )
343+
344+ screen . key ( [ 'escape' , 'q' , 'C-c' ] , ( ) => process . exit ( 0 ) )
345+ }
346+
347+ const renderLineCharts = ( grid : any , screen : any , title : string , coords : number [ ] , data : { [ key : string ] : number } ) => {
174348 const line = grid . set ( ...coords , contrib . line ,
175349 { style :
176350 { line : "cyan" ,
@@ -191,117 +365,9 @@ const renderLineCharts = (grid: any, screen: any, title: string, coords: number[
191365 screen . append ( line )
192366
193367 const lineData = {
194- x : formattedDates ,
195- y : alertsCounts
368+ x : Object . keys ( data ) ,
369+ y : Object . values ( data )
196370 }
197371
198372 line . setData ( [ lineData ] )
199- }
200-
201- type AnalyticsData = {
202- id : number ,
203- created_at : string
204- repository_id : string
205- organization_id : number
206- repository_name : string
207- total_critical_alerts : number
208- total_high_alerts : number
209- total_medium_alerts : number
210- total_low_alerts : number
211- total_critical_added : number
212- total_high_added : number
213- total_medium_added : number
214- total_low_added : number
215- total_critical_prevented : number
216- total_high_prevented : number
217- total_medium_prevented : number
218- total_low_prevented : number
219- top_five_alert_types : {
220- [ key : string ] : number
221- }
222- }
223-
224- type FormattedAnalyticsData = {
225- [ key : string ] : AnalyticsData
226- }
227-
228- const formatData = ( data : AnalyticsData [ ] ) => {
229- return data . reduce ( ( acc : { [ key : string ] : any } , current ) => {
230- const formattedDate = new Date ( current . created_at ) . toLocaleDateString ( )
231-
232- if ( acc [ formattedDate ] ) {
233- acc [ formattedDate ] . total_critical_alerts += current . total_critical_alerts
234- acc [ formattedDate ] . total_high_alerts += current . total_high_alerts
235- acc [ formattedDate ] . total_critical_added += current . total_critical_added
236- acc [ formattedDate ] . total_high_added += current . total_high_added
237- acc [ formattedDate ] . total_critical_prevented += current . total_critical_prevented
238- acc [ formattedDate ] . total_high_prevented += current . total_high_prevented
239- acc [ formattedDate ] . total_medium_prevented += current . total_medium_prevented
240- acc [ formattedDate ] . total_low_prevented += current . total_low_prevented
241- } else {
242- acc [ formattedDate ] = current
243- acc [ formattedDate ] . created_at = formattedDate
244- }
245-
246- return acc
247- } , { } )
248- }
249-
250- const displayAnalyticsScreen = ( data : FormattedAnalyticsData ) => {
251- const screen = blessed . screen ( )
252- // eslint-disable-next-line
253- const grid = new contrib . grid ( { rows : 5 , cols : 4 , screen} )
254-
255- renderLineCharts ( grid , screen , 'Total critical alerts' , [ 0 , 0 , 1 , 2 ] , data , 'total_critical_alerts' )
256- renderLineCharts ( grid , screen , 'Total high alerts' , [ 0 , 2 , 1 , 2 ] , data , 'total_high_alerts' )
257- renderLineCharts ( grid , screen , 'Total critical alerts added to the main branch' , [ 1 , 0 , 1 , 2 ] , data , 'total_critical_added' )
258- renderLineCharts ( grid , screen , 'Total high alerts added to the main branch' , [ 1 , 2 , 1 , 2 ] , data , 'total_high_added' )
259- renderLineCharts ( grid , screen , 'Total critical alerts prevented from the main branch' , [ 2 , 0 , 1 , 2 ] , data , 'total_critical_prevented' )
260- renderLineCharts ( grid , screen , 'Total high alerts prevented from the main branch' , [ 2 , 2 , 1 , 2 ] , data , 'total_high_prevented' )
261- renderLineCharts ( grid , screen , 'Total medium alerts prevented from the main branch' , [ 3 , 0 , 1 , 2 ] , data , 'total_medium_prevented' )
262- renderLineCharts ( grid , screen , 'Total low alerts prevented from the main branch' , [ 3 , 2 , 1 , 2 ] , data , 'total_low_prevented' )
263-
264- const bar = grid . set ( 4 , 0 , 1 , 2 , contrib . bar ,
265- { label : 'Top 5 alert types'
266- , barWidth : 10
267- , barSpacing : 17
268- , xOffset : 0
269- , maxHeight : 9 , barBgColor : 'magenta' } )
270-
271- screen . append ( bar ) //must append before setting data
272-
273- const top5 = extractTop5Alerts ( data )
274-
275- bar . setData (
276- { titles : Object . keys ( top5 )
277- , data : Object . values ( top5 ) } )
278-
279- screen . render ( )
280-
281- screen . key ( [ 'escape' , 'q' , 'C-c' ] , ( ) => process . exit ( 0 ) )
282- }
283-
284- const extractTop5Alerts = ( data : FormattedAnalyticsData ) => {
285- const allTop5Alerts = Object . values ( data ) . map ( d => d . top_five_alert_types )
286-
287- const aggTop5Alerts = allTop5Alerts . reduce ( ( acc , current ) => {
288- const alertTypes = Object . keys ( current )
289-
290- alertTypes . forEach ( type => {
291- if ( ! acc [ type ] ) {
292- // @ts -ignore
293- acc [ type ] = current [ type ]
294- } else {
295- // @ts -ignore
296- if ( acc [ type ] < current [ type ] ) {
297- // @ts -ignore
298- acc [ type ] = current [ type ]
299- }
300- }
301- } )
302-
303- return acc
304- } , { } )
305-
306- return Object . fromEntries ( Object . entries ( aggTop5Alerts ) . sort ( ( a : [ string , number ] , b : [ string , number ] ) => b [ 1 ] - a [ 1 ] ) . slice ( 0 , 5 ) )
307373}
0 commit comments