diff --git a/CSETWebNg/src/app/app-routing.module.ts b/CSETWebNg/src/app/app-routing.module.ts index 62cbae386..748c06a25 100644 --- a/CSETWebNg/src/app/app-routing.module.ts +++ b/CSETWebNg/src/app/app-routing.module.ts @@ -238,6 +238,7 @@ import { TutorialMvraComponent } from './assessment/prepare/maturity/tutorial-mv import { AllAnsweredquestionsComponent } from './reports/all-answeredquestions/all-answeredquestions.component'; import { AllCommentsmarkedComponent } from './reports/all-commentsmarked/all-commentsmarked.component'; import { AllReviewedComponent } from './reports/all-reviewed/all-reviewed.component'; +import { AnalyticsResultsComponent } from './assessment/results/analytics-results/analytics-results.component'; import { Cmmc2LevelsComponent } from './assessment/prepare/maturity/cmmc2-levels/cmmc2-levels.component'; const appRoutes: Routes = [ @@ -470,6 +471,7 @@ const appRoutes: Routes = [ { path: 'mvra-summary-page', component: MvraSummaryPageComponent }, { path: 'cpg-summary-page', component: CpgSummaryComponent }, { path: 'cpg-practices-page', component: CpgPracticesComponent }, + { path: 'analytics-results-page', component: AnalyticsResultsComponent}, { path: 'analysis', component: AnalysisComponent }, { path: 'dashboard', component: DashboardComponent }, { path: 'ranked-questions', component: RankedQuestionsComponent }, diff --git a/CSETWebNg/src/app/app.module.ts b/CSETWebNg/src/app/app.module.ts index 803fbdc06..868f1426a 100644 --- a/CSETWebNg/src/app/app.module.ts +++ b/CSETWebNg/src/app/app.module.ts @@ -677,6 +677,7 @@ import { AllCommentsmarkedComponent } from './reports/all-commentsmarked/all-com import { AllReviewedComponent } from './reports/all-reviewed/all-reviewed.component'; import { QuestionsReviewedComponent } from './reports/questions-reviewed/questions-reviewed.component'; import { RolesChangedComponent } from './dialogs/roles-changed/roles-changed.component'; +import { AnalyticsResultsComponent } from './assessment/results/analytics-results/analytics-results.component'; @NgModule({ declarations: [ @@ -1206,7 +1207,8 @@ import { RolesChangedComponent } from './dialogs/roles-changed/roles-changed.com AllCommentsmarkedComponent, AllReviewedComponent, QuestionsReviewedComponent, - RolesChangedComponent + RolesChangedComponent, + AnalyticsResultsComponent ], bootstrap: [AppComponent], imports: [BrowserModule, BrowserAnimationsModule, diff --git a/CSETWebNg/src/app/assessment/results/analytics-results/analytics-results.component.html b/CSETWebNg/src/app/assessment/results/analytics-results/analytics-results.component.html new file mode 100644 index 000000000..8883b4bfc --- /dev/null +++ b/CSETWebNg/src/app/assessment/results/analytics-results/analytics-results.component.html @@ -0,0 +1,26 @@ +
+ +
+
+
+
+ +

Analytics

+
+ + My Sector + All Sectors + +
+
+ +
+
+
+
+
+ + + +
\ No newline at end of file diff --git a/CSETWebNg/src/app/assessment/results/analytics-results/analytics-results.component.scss b/CSETWebNg/src/app/assessment/results/analytics-results/analytics-results.component.scss new file mode 100644 index 000000000..2c3d1d589 --- /dev/null +++ b/CSETWebNg/src/app/assessment/results/analytics-results/analytics-results.component.scss @@ -0,0 +1,16 @@ +.mat-card-margin-10 { + margin: 10px; + float: left; + width: 99%; + } + .mat-row:hover { + background-color: #f2f2f2; + cursor: pointer; + } + + .table-container { + position: relative; + height: 350px; + max-height: 350px; + overflow: auto; + } \ No newline at end of file diff --git a/CSETWebNg/src/app/assessment/results/analytics-results/analytics-results.component.ts b/CSETWebNg/src/app/assessment/results/analytics-results/analytics-results.component.ts new file mode 100644 index 000000000..0d6513cf2 --- /dev/null +++ b/CSETWebNg/src/app/assessment/results/analytics-results/analytics-results.component.ts @@ -0,0 +1,152 @@ +import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; +import { AnalyticsService } from '../../../services/analytics.service'; +import { NavigationService } from '../../../services/navigation/navigation.service'; +import Chart, { ChartConfiguration, ChartType, registerables } from 'chart.js/auto'; +import { AssessmentService } from '../../../services/assessment.service'; +import { AggregationService } from '../../../services/aggregation.service'; +import { AssessmentDetail } from '../../../models/assessment-info.model'; + +Chart.register(...registerables); + +@Component({ + selector: 'app-analytics-results', + templateUrl: './analytics-results.component.html', + styleUrls: ['./analytics-results.component.scss'] +}) +export class AnalyticsResultsComponent implements OnInit { + + sectorId: any; + assessmentId: any; + modelId: any; + minData: number[] = []; + medianData: number[] = []; + maxData: number[] = []; + currentUserData: number[] = []; + labels: string[] = []; + + @ViewChild('barCanvas') private barCanvas!: ElementRef; + private barChart!: Chart; + + // Toggle state + dataType: "mySector" | "allSectors" = "mySector"; + + constructor( + public navSvc: NavigationService, + public analyticsSvc: AnalyticsService, + public assessSvc: AssessmentService, + public aggregSvc: AggregationService + ) { } + + ngOnInit(): void { + this.assessSvc.getAssessmentDetail().subscribe((resp: AssessmentDetail) => { + this.assessmentId = resp.id; + this.sectorId = resp.sectorId; + this.modelId = resp.maturityModel.modelId; + // Fetch initial data after getting assessment details + this.getAnalyticsResults(); + }); + } + + ngAfterViewInit(): void { + // Initialize the chart after the view is initialized + this.initializeChart(); + } + + // Get analytics results for specified sector + private async getAnalyticsResults(allSectors?: boolean): Promise { + try { + let result = null; + if (allSectors){ + result = await this.analyticsSvc.getAnalyticResults(this.assessmentId, this.modelId).toPromise(); + } else { + result = await this.analyticsSvc.getAnalyticResults(this.assessmentId, this.modelId, this.sectorId).toPromise(); + } + this.setData(result); + } catch (error) { + console.error('Error fetching analytics results', error); + } + } + + private setData(result: any): void { + this.minData = result.min || []; + this.medianData = result.median || []; + this.maxData = result.max || []; + this.currentUserData = result.barData?.values || []; + this.labels = result.barData?.labels || []; + if (this.barChart) { + this.updateChart(); + } + } + + private initializeChart(): void { + const data: ChartConfiguration<'bar'>['data'] = { + labels: this.labels, + datasets: [ + { + label: 'Min', + data: this.minData, + backgroundColor: 'rgba(178, 29, 45, 1)', + borderWidth: 1 + }, + { + label: 'Median', + data: this.medianData, + backgroundColor: 'rgba(216, 173, 30, 1)', + borderWidth: 1 + }, + { + label: 'Max', + data: this.maxData, + backgroundColor: 'rgba(16, 145, 71, 1)', + borderWidth: 1 + }, + { + label: 'My Assessment', + data: this.currentUserData, + backgroundColor: 'rgba(29, 136, 230, 1)', + borderWidth: 1 + } + ] + }; + + const options: ChartConfiguration<'bar'>['options'] = { + indexAxis: 'y', + responsive: true, + scales: { + x: { + beginAtZero: true, + min: 0, + max: 100 + }, + y: { + beginAtZero: true, + } + } + }; + + this.barChart = new Chart(this.barCanvas.nativeElement, { + type: 'bar' as ChartType, + data: data, + options: options + }); + } + + private updateChart(): void { + this.barChart.data.labels = this.labels; + this.barChart.data.datasets[0].data = this.minData; + this.barChart.data.datasets[1].data = this.medianData; + this.barChart.data.datasets[2].data = this.maxData; + this.barChart.data.datasets[3].data = this.currentUserData; + this.barChart.update(); + } + + toggleData(event: any): void { + this.dataType = event.value; + if (this.dataType === "allSectors") { + this.getAnalyticsResults(true); + } else { + this.getAnalyticsResults(); + } + } + +} diff --git a/CSETWebNg/src/app/services/analytics.service.ts b/CSETWebNg/src/app/services/analytics.service.ts index 69bc7b266..0a98f6411 100644 --- a/CSETWebNg/src/app/services/analytics.service.ts +++ b/CSETWebNg/src/app/services/analytics.service.ts @@ -26,6 +26,16 @@ export class AnalyticsService { return this.http.get(this.apiUrl + 'getAggregation'); } + + getAnalyticResults(assessmentId: any, maturityModelId: any, sectorId?: any): any { + let url = `http://csetac:5210/api/analytics/maturity?modelId=${maturityModelId}&assessmentId=${assessmentId}`; + + if (sectorId) { + url += `§orId=${sectorId}`; + } + return this.http.get(url); +} + getAnalyticsToken(username, password): any { return this.http.post( this.analyticsUrl + 'auth/login', { username, password }, this.headers diff --git a/CSETWebNg/src/assets/navigation/workflow-omni.xml b/CSETWebNg/src/assets/navigation/workflow-omni.xml index b61aed12c..c33e10a44 100644 --- a/CSETWebNg/src/assets/navigation/workflow-omni.xml +++ b/CSETWebNg/src/assets/navigation/workflow-omni.xml @@ -401,6 +401,7 @@ +