Skip to content

Commit e04e0ff

Browse files
[#2376085] Metrics add audit count by auditor
1 parent e24c4d7 commit e04e0ff

File tree

6 files changed

+267
-2
lines changed

6 files changed

+267
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<template>
2+
<div>
3+
<div class="col-md-2 pt-2 text-left page-title">
4+
<h3><small class="text-nowrap">Audit Metrics</small></h3>
5+
</div>
6+
<div class="pl-2">
7+
<div class="row">
8+
<div class="col-md-5 pt-2">
9+
<h5><small class="text-nowrap">Audits by Auditor per week</small></h5>
10+
<Spinner :active="!loadedAuditCounts" />
11+
<MultiLineChart
12+
v-if="loadedAuditCounts"
13+
:chart-data="chartDataForAuditCountsGraph"
14+
:chart-options="chartOptions"
15+
/>
16+
</div>
17+
</div>
18+
</div>
19+
</div>
20+
</template>
21+
22+
<script>
23+
import AxiosConfig from '@/configuration/axios-config.js';
24+
import FindingsService from '@/services/findings-service';
25+
import MultiLineChart from '@/components/Charts/MultiLineChart.vue';
26+
import Spinner from '@/components/Common/Spinner.vue';
27+
28+
export default {
29+
name: 'AuditMetrics',
30+
components: {
31+
MultiLineChart,
32+
Spinner,
33+
},
34+
data() {
35+
return {
36+
loadedAuditCounts: false,
37+
loadedAuditCountsAuditors: false,
38+
chartDataForAuditCountsGraph: { labels: [], datasets: [] },
39+
auditCounts: [],
40+
chartOptions: {
41+
responsive: true,
42+
maintainAspectRatio: false,
43+
},
44+
};
45+
},
46+
methods: {
47+
arrayContainsAllZeros(arr) {
48+
return arr.every((item) => item === 0);
49+
},
50+
getGraphData() {
51+
FindingsService.getAuditsByAuditorPerWeek()
52+
.then((response) => {
53+
this.auditCounts = response.data;
54+
let datasets = {};
55+
this.auditCounts.forEach((data) => {
56+
this.chartDataForAuditCountsGraph['labels'].push(data.time_period);
57+
58+
if (!this.loadedAuditCountsAuditors) {
59+
Object.entries(data.audit_by_auditor_count).forEach((auditorData) => {
60+
datasets[auditorData[0]] = this.prepareDataSet(auditorData[0], auditorData[1]);
61+
});
62+
datasets['Total'] = this.prepareDataSet('Total', data.total);
63+
this.loadedAuditCountsAuditors = true;
64+
} else {
65+
Object.entries(data.audit_by_auditor_count).forEach((auditorData) => {
66+
datasets[auditorData[0]].data.push(auditorData[1]);
67+
});
68+
datasets['Total'].data.push(data.total);
69+
}
70+
});
71+
Object.entries(datasets).forEach((auditorDataset) => {
72+
this.chartDataForAuditCountsGraph.datasets.push(auditorDataset[1]);
73+
});
74+
this.loadedAuditCounts = true;
75+
})
76+
.catch((error) => {
77+
AxiosConfig.handleError(error);
78+
});
79+
},
80+
prepareDataSet(datasetLabel, datasetFirstValue) {
81+
const datasetsObj = {};
82+
let colourCode = '#' + Math.floor(Math.random() * 16777215).toString(16);
83+
datasetsObj.borderWidth = 1.5;
84+
datasetsObj.cubicInterpolationMode = 'monotone';
85+
datasetsObj.data = [datasetFirstValue];
86+
datasetsObj.pointStyle = 'circle';
87+
datasetsObj.pointRadius = 3;
88+
datasetsObj.pointHoverRadius = 8;
89+
datasetsObj.label = datasetLabel;
90+
91+
if (datasetLabel === 'Total') {
92+
datasetsObj.hidden = true;
93+
}
94+
datasetsObj.borderColor = colourCode;
95+
datasetsObj.pointBackgroundColor = colourCode;
96+
datasetsObj.backgroundColor = colourCode;
97+
98+
return datasetsObj;
99+
},
100+
},
101+
mounted() {
102+
this.getGraphData();
103+
},
104+
};
105+
</script>

components/resc-frontend/src/components/Navigation/Navigation.vue

+12-2
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ export default {
1717
},
1818
child: [
1919
{
20-
href: '/metrics/rule-metrics',
21-
title: 'Rule Metrics',
20+
href: '/metrics/audit-metrics',
21+
title: 'Audit Metrics',
2222
icon: {
2323
element: 'font-awesome-icon',
2424
attributes: {
@@ -36,6 +36,16 @@ export default {
3636
},
3737
},
3838
},
39+
{
40+
href: '/metrics/rule-metrics',
41+
title: 'Rule Metrics',
42+
icon: {
43+
element: 'font-awesome-icon',
44+
attributes: {
45+
icon: 'chart-bar',
46+
},
47+
},
48+
},
3949
],
4050
},
4151
{

components/resc-frontend/src/router/index.js

+6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import Vue from 'vue';
22
import VueRouter from 'vue-router';
33
import Config from '@/configuration/config';
44
import FindingMetrics from '@/components/Metrics/FindingMetrics';
5+
import AuditMetrics from '@/components/Metrics/AuditMetrics';
56
import Store from '@/store/index.js';
67
import Analytics from '@/views/Analytics';
78
import Repositories from '@/views/Repositories';
@@ -49,6 +50,11 @@ const routes = [
4950
name: 'FindingMetrics',
5051
component: FindingMetrics,
5152
},
53+
{
54+
path: '/metrics/audit-metrics',
55+
name: 'AuditMetrics',
56+
component: AuditMetrics,
57+
},
5258
{
5359
path: '/rulepacks',
5460
name: 'RulePacks',

components/resc-frontend/src/services/findings-service.js

+3
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ const FindingsService = {
8787
async getTruePositiveCountPerVcsProviderPerWeek() {
8888
return axios.get(`/metrics/audited-count-over-time`);
8989
},
90+
async getAuditsByAuditorPerWeek() {
91+
return axios.get(`/metrics/audit-count-by-auditor-over-time`);
92+
},
9093

9194
async getFindingAudits(findingId, perPage, skipRowCount) {
9295
return axios.get(`findings/${findingId}/audit`, {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
[
2+
{
3+
"time_period": "2023 W12",
4+
"audit_by_auditor_count": {
5+
"Anonymous": 0,
6+
"Me": 0
7+
},
8+
"total": 0
9+
},
10+
{
11+
"time_period": "2023 W13",
12+
"audit_by_auditor_count": {
13+
"Anonymous": 0,
14+
"Me": 0
15+
},
16+
"total": 0
17+
},
18+
{
19+
"time_period": "2023 W14",
20+
"audit_by_auditor_count": {
21+
"Anonymous": 0,
22+
"Me": 0
23+
},
24+
"total": 0
25+
},
26+
{
27+
"time_period": "2023 W15",
28+
"audit_by_auditor_count": {
29+
"Anonymous": 4,
30+
"Me": 1
31+
},
32+
"total": 5
33+
},
34+
{
35+
"time_period": "2023 W16",
36+
"audit_by_auditor_count": {
37+
"Anonymous": 0,
38+
"Me": 0
39+
},
40+
"total": 0
41+
},
42+
{
43+
"time_period": "2023 W17",
44+
"audit_by_auditor_count": {
45+
"Anonymous": 0,
46+
"Me": 0
47+
},
48+
"total": 0
49+
},
50+
{
51+
"time_period": "2023 W18",
52+
"audit_by_auditor_count": {
53+
"Anonymous": 0,
54+
"Me": 0
55+
},
56+
"total": 0
57+
},
58+
{
59+
"time_period": "2023 W19",
60+
"audit_by_auditor_count": {
61+
"Anonymous": 0,
62+
"Me": 0
63+
},
64+
"total": 0
65+
},
66+
{
67+
"time_period": "2023 W20",
68+
"audit_by_auditor_count": {
69+
"Anonymous": 13,
70+
"Me": 0
71+
},
72+
"total": 13
73+
},
74+
{
75+
"time_period": "2023 W21",
76+
"audit_by_auditor_count": {
77+
"Anonymous": 0,
78+
"Me": 0
79+
},
80+
"total": 0
81+
},
82+
{
83+
"time_period": "2023 W22",
84+
"audit_by_auditor_count": {
85+
"Anonymous": 0,
86+
"Me": 0
87+
},
88+
"total": 0
89+
},
90+
{
91+
"time_period": "2023 W23",
92+
"audit_by_auditor_count": {
93+
"Anonymous": 0,
94+
"Me": 0
95+
},
96+
"total": 0
97+
},
98+
{
99+
"time_period": "2023 W24",
100+
"audit_by_auditor_count": {
101+
"Anonymous": 0,
102+
"Me": 0
103+
},
104+
"total": 0
105+
}
106+
]

components/resc-frontend/tests/unit/services/findings-service.spec.js

+35
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import detailed_findings_with_rule_pack_version from '@/../tests/resources/mock_
66
import finding_count_per_week from '@/../tests/resources/mock_findings_count_per_week.json';
77
import audits from '@/../tests/resources/mock_finding_audits.json';
88
import findings_status_counts_per_vcs_provider_per_week from '@/../tests/resources/mock_findings_status_count_by_vcs_provider_per_week.json';
9+
import audit_count_by_auditor_per_week from '@/../tests/resources/mock_audit_count_by_auditor_per_week.json';
910

1011
jest.mock('axios');
1112

@@ -273,5 +274,39 @@ describe('function getDetailedFindings', () => {
273274
});
274275
});
275276
});
277+
278+
describe('function getAuditsByAuditorPerWeek', () => {
279+
describe('when getAuditsByAuditorPerWeek API call is successful', () => {
280+
it('should return audit counts per auditor per week', async () => {
281+
axios.get.mockResolvedValueOnce(audit_count_by_auditor_per_week);
282+
283+
const response = await FindingsService.getAuditsByAuditorPerWeek();
284+
285+
expect(response).toEqual(audit_count_by_auditor_per_week);
286+
expect(response).toBeDefined();
287+
expect(response).not.toBeNull();
288+
expect(response.length).toBe(13);
289+
expect(response[0].time_period).toBe('2023 W12');
290+
expect(response[0].total).toBe(0);
291+
expect(response[0].audit_by_auditor_count.Anonymous).toBe(0);
292+
});
293+
});
294+
295+
describe('when getAuditsByAuditorPerWeek API call fails', () => {
296+
it('getAuditsByAuditorPerWeek should return error', async () => {
297+
axios.get.mockResolvedValueOnce([]);
298+
299+
await FindingsService.getAuditsByAuditorPerWeek('not_valid')
300+
.then((response) => {
301+
expect(response).toEqual([]);
302+
expect(response).not.toBeNull();
303+
})
304+
.catch((error) => {
305+
expect(error).toBeDefined();
306+
expect(error).not.toBeNull();
307+
});
308+
});
309+
});
310+
});
276311
});
277312
});

0 commit comments

Comments
 (0)