Skip to content

Commit da85d1e

Browse files
committed
improvements
1 parent 76f1805 commit da85d1e

File tree

1 file changed

+185
-119
lines changed

1 file changed

+185
-119
lines changed

src/commands/analytics.ts

Lines changed: 185 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// @ts-nocheck
12
// @ts-ignore
23
import blessed from 'blessed'
34
import 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+
123170
async 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+
146293
async 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

Comments
 (0)