Skip to content

Commit

Permalink
Merge pull request #4224 from cisagov/feat/CSET-2905
Browse files Browse the repository at this point in the history
Analytics Chart for CPG - Feat/cset 2905
  • Loading branch information
randywoods authored Nov 12, 2024
2 parents c74d67d + 1312a87 commit 7bd0fd2
Show file tree
Hide file tree
Showing 7 changed files with 210 additions and 1 deletion.
2 changes: 2 additions & 0 deletions CSETWebNg/src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down Expand Up @@ -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 },
Expand Down
4 changes: 3 additions & 1 deletion CSETWebNg/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand Down Expand Up @@ -1206,7 +1207,8 @@ import { RolesChangedComponent } from './dialogs/roles-changed/roles-changed.com
AllCommentsmarkedComponent,
AllReviewedComponent,
QuestionsReviewedComponent,
RolesChangedComponent
RolesChangedComponent,
AnalyticsResultsComponent
],
bootstrap: [AppComponent], imports: [BrowserModule,
BrowserAnimationsModule,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<div class="white-panel oy-auto ox-auto d-flex flex-column flex-11a">

<div class="container">
<div class="container-fluid">
<div class="row">
<div class="col-sm">
<mat-card>
<mat-card-header class="d-flex justify-content-center"><h2>Analytics</h2></mat-card-header>
<div class="p-4">
<mat-button-toggle-group [(ngModel)]="dataType" (change)="toggleData($event)">
<mat-button-toggle value="mySector">My Sector</mat-button-toggle>
<mat-button-toggle value="allSectors">All Sectors</mat-button-toggle>
</mat-button-toggle-group>
</div>
<div style="display: block;">
<canvas #barCanvas></canvas>
</div>
</mat-card>
</div>
</div>
</div>

<app-nav-back-next [page]="'analytics-results-page'"
[hide]="navSvc.isLastVisiblePage('analytics') ? 'next' : ''"></app-nav-back-next>

</div>
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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<HTMLCanvasElement>;
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<void> {
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();
}
}

}
10 changes: 10 additions & 0 deletions CSETWebNg/src/app/services/analytics.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 += `&sectorId=${sectorId}`;
}
return this.http.get(url);
}

getAnalyticsToken(username, password): any {
return this.http.post(
this.analyticsUrl + 'auth/login', { username, password }, this.headers
Expand Down
1 change: 1 addition & 0 deletions CSETWebNg/src/assets/navigation/workflow-omni.xml
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,7 @@
<!-- Results - CPG -->
<node d="performance summary" id="cpg-summary-page" path="assessment/{:id}/results/cpg-summary-page" visible="MATURITY:11" />
<node d="security practice checklist" id="cpg-practices-page" path="assessment/{:id}/results/cpg-practices-page" visible="MATURITY:11" />
<node d="analytics" id="analytics-results-page" path="assessment/{:id}/results/analytics-results-page" visible="MATURITY:11" />

<!-- Results - SD02 Series -->
<node displaytext="SD02 Series Pipeline Answer Summary" id="sd-answer-summary" path="assessment/{:id}/results/sd-answer-summary" visible="MATURITY:14" />
Expand Down

0 comments on commit 7bd0fd2

Please sign in to comment.