Skip to content

Commit 649ced6

Browse files
committed
Cleanup analytics command
1 parent 8b1c401 commit 649ced6

File tree

1 file changed

+174
-196
lines changed

1 file changed

+174
-196
lines changed

src/commands/analytics.ts

Lines changed: 174 additions & 196 deletions
Original file line numberDiff line numberDiff line change
@@ -168,49 +168,9 @@ const METRICS = [
168168
'total_high_prevented',
169169
'total_medium_prevented',
170170
'total_low_prevented'
171-
]
171+
] as const
172172

173-
async function fetchOrgAnalyticsData(
174-
time: number,
175-
spinner: Spinner,
176-
apiToken: string,
177-
outputJson: boolean,
178-
filePath: string
179-
): Promise<void> {
180-
const socketSdk = await setupSdk(apiToken)
181-
const result = await handleApiCall(
182-
socketSdk.getOrgAnalytics(time.toString()),
183-
'fetching analytics data'
184-
)
185-
186-
if (result.success === false) {
187-
return handleUnsuccessfulApiResponse('getOrgAnalytics', result, spinner)
188-
}
189-
190-
spinner.stop()
191-
192-
if (!result.data.length) {
193-
return console.log(
194-
'No analytics data is available for this organization yet.'
195-
)
196-
}
197-
const data = formatData(result.data, 'org')
198-
if (outputJson && !filePath) {
199-
return console.log(result.data)
200-
}
201-
if (filePath) {
202-
try {
203-
await fs.writeFile(filePath, JSON.stringify(result.data), 'utf8')
204-
console.log(`Data successfully written to ${filePath}`)
205-
} catch (e: any) {
206-
console.error(e)
207-
}
208-
return
209-
}
210-
return displayAnalyticsScreen(data)
211-
}
212-
213-
const months = [
173+
const MONTHS = [
214174
'Jan',
215175
'Feb',
216176
'Mar',
@@ -223,159 +183,9 @@ const months = [
223183
'Oct',
224184
'Nov',
225185
'Dec'
226-
]
227-
228-
const formatDate = (date: string) => {
229-
return `${months[new Date(date).getMonth()]} ${new Date(date).getDate()}`
230-
}
231-
232-
const formatData = (data: any, scope: string) => {
233-
let formattedData, sortedTopFiveAlerts
234-
235-
if (scope === 'org') {
236-
const topFiveAlerts = data.map(
237-
(d: { [k: string]: any }) => d['top_five_alert_types']
238-
)
239-
240-
const totalTopAlerts: { [key: string]: number } = topFiveAlerts.reduce(
241-
(acc: { [k: string]: number }, current: { [key: string]: number }) => {
242-
const alertTypes = Object.keys(current)
243-
alertTypes.map((type: string) => {
244-
if (!acc[type]) {
245-
acc[type] = current[type]!
246-
} else {
247-
acc[type] += current[type]!
248-
}
249-
return acc
250-
})
251-
return acc
252-
},
253-
{} as { [k: string]: number }
254-
)
255-
256-
sortedTopFiveAlerts = Object.entries(totalTopAlerts)
257-
.sort(({ 1: a }, { 1: b }) => b - a)
258-
.slice(0, 5)
259-
.reduce(
260-
(r, { 0: k, 1: v }) => {
261-
r[k] = v
262-
return r
263-
},
264-
{} as typeof totalTopAlerts
265-
)
266-
267-
const formatData = (label: string) => {
268-
return data.reduce(
269-
(acc: { [k: string]: number }, current: { [key: string]: any }) => {
270-
const date: string = formatDate(current['created_at'])
271-
if (!acc[date]) {
272-
acc[date] = current[label]!
273-
} else {
274-
acc[date] += current[label]!
275-
}
276-
return acc
277-
},
278-
{}
279-
)
280-
}
281-
282-
formattedData = METRICS.reduce(
283-
(acc, current: string) => {
284-
acc[current] = formatData(current)
285-
return acc
286-
},
287-
{} as { [k: string]: number }
288-
)
289-
} else if (scope === 'repo') {
290-
const topAlerts: { [key: string]: number } = data.reduce(
291-
(acc: { [key: string]: number }, current: { [key: string]: any }) => {
292-
const alertTypes = Object.keys(current['top_five_alert_types'])
293-
alertTypes.map(type => {
294-
if (!acc[type]) {
295-
acc[type] = current['top_five_alert_types'][type]
296-
} else {
297-
if (current['top_five_alert_types'][type] > (acc[type] || 0)) {
298-
acc[type] = current['top_five_alert_types'][type]
299-
}
300-
}
301-
return acc
302-
})
303-
return acc
304-
},
305-
{} as { [key: string]: number }
306-
)
307-
308-
sortedTopFiveAlerts = Object.entries(topAlerts)
309-
.sort(({ 1: a }, { 1: b }) => b - a)
310-
.slice(0, 5)
311-
.reduce(
312-
(r, { 0: k, 1: v }) => {
313-
r[k] = v
314-
return r
315-
},
316-
{} as typeof topAlerts
317-
)
318-
319-
formattedData = data.reduce(
320-
(acc: any, current: { [key: string]: any }) => {
321-
METRICS.forEach((m: string) => {
322-
if (!acc[m]) {
323-
acc[m] = {}
324-
}
325-
acc[m][formatDate(current['created_at'])] = current[m]
326-
return acc
327-
})
328-
return acc
329-
},
330-
{} as { [k: string]: number }
331-
)
332-
}
333-
334-
return { ...formattedData, top_five_alert_types: sortedTopFiveAlerts }
335-
}
336-
337-
async function fetchRepoAnalyticsData(
338-
repo: string,
339-
time: number,
340-
spinner: Spinner,
341-
apiToken: string,
342-
outputJson: boolean,
343-
filePath: string
344-
): Promise<void> {
345-
const socketSdk = await setupSdk(apiToken)
346-
const result = await handleApiCall(
347-
socketSdk.getRepoAnalytics(repo, time.toString()),
348-
'fetching analytics data'
349-
)
350-
351-
if (result.success === false) {
352-
return handleUnsuccessfulApiResponse('getRepoAnalytics', result, spinner)
353-
}
354-
355-
spinner.stop()
356-
357-
if (!result.data.length) {
358-
return console.log(
359-
'No analytics data is available for this organization yet.'
360-
)
361-
}
362-
const data = formatData(result.data, 'repo')
363-
if (outputJson && !filePath) {
364-
return console.log(result.data)
365-
}
366-
if (filePath) {
367-
try {
368-
await fs.writeFile(filePath, JSON.stringify(result.data), 'utf8')
369-
console.log(`Data successfully written to ${filePath}`)
370-
} catch (e: any) {
371-
console.error(e)
372-
}
373-
return
374-
}
375-
return displayAnalyticsScreen(data)
376-
}
186+
] as const
377187

378-
const displayAnalyticsScreen = (data: any) => {
188+
function displayAnalyticsScreen(data: any) {
379189
const screen = new ScreenWidget()
380190
const grid = new GridLayout({ rows: 5, cols: 4, screen })
381191

@@ -457,13 +267,181 @@ const displayAnalyticsScreen = (data: any) => {
457267
screen.key(['escape', 'q', 'C-c'], () => process.exit(0))
458268
}
459269

460-
const renderLineCharts = (
270+
async function fetchOrgAnalyticsData(
271+
time: number,
272+
spinner: Spinner,
273+
apiToken: string,
274+
outputJson: boolean,
275+
filePath: string
276+
): Promise<void> {
277+
const socketSdk = await setupSdk(apiToken)
278+
const result = await handleApiCall(
279+
socketSdk.getOrgAnalytics(time.toString()),
280+
'fetching analytics data'
281+
)
282+
283+
if (result.success === false) {
284+
return handleUnsuccessfulApiResponse('getOrgAnalytics', result, spinner)
285+
}
286+
287+
spinner.stop()
288+
289+
if (!result.data.length) {
290+
return console.log(
291+
'No analytics data is available for this organization yet.'
292+
)
293+
}
294+
const data = formatData(result.data, 'org')
295+
if (outputJson && !filePath) {
296+
console.log(result.data)
297+
return
298+
}
299+
if (filePath) {
300+
try {
301+
await fs.writeFile(filePath, JSON.stringify(result.data), 'utf8')
302+
console.log(`Data successfully written to ${filePath}`)
303+
} catch (e: any) {
304+
console.error(e)
305+
}
306+
return
307+
}
308+
return displayAnalyticsScreen(data)
309+
}
310+
311+
async function fetchRepoAnalyticsData(
312+
repo: string,
313+
time: number,
314+
spinner: Spinner,
315+
apiToken: string,
316+
outputJson: boolean,
317+
filePath: string
318+
): Promise<void> {
319+
const socketSdk = await setupSdk(apiToken)
320+
const result = await handleApiCall(
321+
socketSdk.getRepoAnalytics(repo, time.toString()),
322+
'fetching analytics data'
323+
)
324+
325+
if (result.success === false) {
326+
return handleUnsuccessfulApiResponse('getRepoAnalytics', result, spinner)
327+
}
328+
329+
spinner.stop()
330+
331+
if (!result.data.length) {
332+
return console.log(
333+
'No analytics data is available for this organization yet.'
334+
)
335+
}
336+
const data = formatData(result.data, 'repo')
337+
if (outputJson && !filePath) {
338+
return console.log(result.data)
339+
}
340+
if (filePath) {
341+
try {
342+
await fs.writeFile(filePath, JSON.stringify(result.data), 'utf8')
343+
console.log(`Data successfully written to ${filePath}`)
344+
} catch (e: any) {
345+
console.error(e)
346+
}
347+
return
348+
}
349+
return displayAnalyticsScreen(data)
350+
}
351+
352+
type FormattedData = {
353+
top_five_alert_types: { [key: string]: number }
354+
total_critical_alerts: { [key: string]: number }
355+
total_high_alerts: { [key: string]: number }
356+
total_medium_alerts: { [key: string]: number }
357+
total_low_alerts: { [key: string]: number }
358+
total_critical_added: { [key: string]: number }
359+
total_medium_added: { [key: string]: number }
360+
total_low_added: { [key: string]: number }
361+
total_high_added: { [key: string]: number }
362+
total_critical_prevented: { [key: string]: number }
363+
total_high_prevented: { [key: string]: number }
364+
total_medium_prevented: { [key: string]: number }
365+
total_low_prevented: { [key: string]: number }
366+
}
367+
368+
function formatData(
369+
data: { [key: string]: any }[],
370+
scope: string
371+
): FormattedData {
372+
const formattedData = <Omit<FormattedData, 'top_five_alert_types'>>{}
373+
const sortedTopFiveAlerts: { [key: string]: number } = {}
374+
const totalTopAlerts: { [key: string]: number } = {}
375+
376+
for (const metric of METRICS) {
377+
formattedData[metric] = {}
378+
}
379+
if (scope === 'org') {
380+
for (const entry of data) {
381+
const topFiveAlertTypes = entry['top_five_alert_types']
382+
for (const type of Object.keys(topFiveAlertTypes)) {
383+
const count = topFiveAlertTypes[type] ?? 0
384+
if (!totalTopAlerts[type]) {
385+
totalTopAlerts[type] = count
386+
} else {
387+
totalTopAlerts[type] += count
388+
}
389+
}
390+
}
391+
for (const metric of METRICS) {
392+
const formatted = formattedData[metric]
393+
for (const entry of data) {
394+
const date = formatDate(entry['created_at'])
395+
if (!formatted[date]) {
396+
formatted[date] = entry[metric]!
397+
} else {
398+
formatted[date] += entry[metric]!
399+
}
400+
}
401+
}
402+
} else if (scope === 'repo') {
403+
for (const entry of data) {
404+
const topFiveAlertTypes = entry['top_five_alert_types']
405+
for (const type of Object.keys(topFiveAlertTypes)) {
406+
const count = topFiveAlertTypes[type] ?? 0
407+
if (!totalTopAlerts[type]) {
408+
totalTopAlerts[type] = count
409+
} else if (count > (totalTopAlerts[type] ?? 0)) {
410+
totalTopAlerts[type] = count
411+
}
412+
}
413+
}
414+
for (const entry of data) {
415+
for (const metric of METRICS) {
416+
formattedData[metric]![formatDate(entry['created_at'])] = entry[metric]
417+
}
418+
}
419+
}
420+
421+
const topFiveAlertEntries = Object.entries(totalTopAlerts)
422+
.sort(({ 1: a }, { 1: b }) => b - a)
423+
.slice(0, 5)
424+
for (const { 0: key, 1: value } of topFiveAlertEntries) {
425+
sortedTopFiveAlerts[key] = value
426+
}
427+
428+
return {
429+
...formattedData,
430+
top_five_alert_types: sortedTopFiveAlerts
431+
}
432+
}
433+
434+
function formatDate(date: string) {
435+
return `${MONTHS[new Date(date).getMonth()]} ${new Date(date).getDate()}`
436+
}
437+
438+
function renderLineCharts(
461439
grid: any,
462440
screen: any,
463441
title: string,
464442
coords: number[],
465443
data: { [key: string]: number }
466-
) => {
444+
) {
467445
const line = grid.set(...coords, LineChart, {
468446
style: { line: 'cyan', text: 'cyan', baseline: 'black' },
469447
xLabelPadding: 0,

0 commit comments

Comments
 (0)