diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index c4911cc82..ecc93f8b4 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -22,6 +22,12 @@ import { AboutUsComponent } from './component/about-us/about-us.component';
import { DependencyGraphComponent } from './component/dependency-graph/dependency-graph.component';
import { TeamsComponent } from './component/teams/teams.component';
import { ToStringValuePipe } from './pipe/to-string-value.pipe';
+import { ModalMessageComponent } from './component/modal-message/modal-message.component';
+import {
+ MatDialogModule,
+ MAT_DIALOG_DATA,
+ MatDialogRef,
+} from '@angular/material/dialog';
@NgModule({
declarations: [
@@ -40,16 +46,23 @@ import { ToStringValuePipe } from './pipe/to-string-value.pipe';
TeamsComponent,
ToStringValuePipe,
UserdayComponent,
+ ModalMessageComponent,
],
imports: [
BrowserModule,
AppRoutingModule,
BrowserAnimationsModule,
MaterialModule,
+ MatDialogModule,
ReactiveFormsModule,
HttpClientModule,
],
- providers: [ymlService],
+ providers: [
+ ymlService,
+ ModalMessageComponent,
+ { provide: MAT_DIALOG_DATA, useValue: {} },
+ { provide: MatDialogRef, useValue: { close: (dialogResult: any) => {} } },
+ ],
bootstrap: [AppComponent],
})
export class AppModule {}
diff --git a/src/app/component/circular-heatmap/circular-heatmap.component.html b/src/app/component/circular-heatmap/circular-heatmap.component.html
index 6e6347d93..43ba21b35 100644
--- a/src/app/component/circular-heatmap/circular-heatmap.component.html
+++ b/src/app/component/circular-heatmap/circular-heatmap.component.html
@@ -284,8 +284,8 @@
Nothing to show
class="normal-button"
mat-raised-button
class="resetButtonClass"
- (click)="ResetIsImplemented()">
- Reset Implemented
+ (click)="deleteLocalTeamsProgress()">
+ Delete team progress
diff --git a/src/app/component/circular-heatmap/circular-heatmap.component.spec.ts b/src/app/component/circular-heatmap/circular-heatmap.component.spec.ts
index a1d1515d0..cb10751cb 100644
--- a/src/app/component/circular-heatmap/circular-heatmap.component.spec.ts
+++ b/src/app/component/circular-heatmap/circular-heatmap.component.spec.ts
@@ -4,6 +4,7 @@ import { ymlService } from 'src/app/service/yaml-parser/yaml-parser.service';
import { CircularHeatmapComponent } from './circular-heatmap.component';
import { RouterTestingModule } from '@angular/router/testing';
import { MatChip } from '@angular/material/chips';
+import { ModalMessageComponent } from '../modal-message/modal-message.component';
describe('CircularHeatmapComponent', () => {
let component: CircularHeatmapComponent;
@@ -11,19 +12,17 @@ describe('CircularHeatmapComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
- providers: [ymlService, HttpClient, HttpHandler],
+ declarations: [CircularHeatmapComponent, MatChip],
imports: [RouterTestingModule],
- declarations: [CircularHeatmapComponent],
+ providers: [
+ ymlService,
+ HttpClient,
+ HttpHandler,
+ { provide: ModalMessageComponent, useValue: {} },
+ ],
}).compileComponents();
- });
- beforeEach(async () => {
- TestBed.configureTestingModule({
- declarations: [MatChip],
- }).compileComponents();
- });
- beforeEach(() => {
- fixture = TestBed.createComponent(CircularHeatmapComponent);
+ fixture = TestBed.createComponent(CircularHeatmapComponent); // Create fixture and component here
component = fixture.componentInstance;
fixture.detectChanges();
});
diff --git a/src/app/component/circular-heatmap/circular-heatmap.component.ts b/src/app/component/circular-heatmap/circular-heatmap.component.ts
index 9a747b519..fa5e33da0 100644
--- a/src/app/component/circular-heatmap/circular-heatmap.component.ts
+++ b/src/app/component/circular-heatmap/circular-heatmap.component.ts
@@ -11,6 +11,10 @@ import * as yaml from 'js-yaml';
import { Router } from '@angular/router';
import { MatChip } from '@angular/material/chips';
import * as md from 'markdown-it';
+import {
+ ModalMessageComponent,
+ DialogInfo,
+} from '../modal-message/modal-message.component';
export interface activitySchema {
uuid: string;
@@ -62,7 +66,7 @@ export class CircularHeatmapComponent implements OnInit {
constructor(
private yaml: ymlService,
private router: Router,
- private changeDetector: ChangeDetectorRef
+ public modal: ModalMessageComponent
) {
this.showOverlay = false;
}
@@ -83,6 +87,14 @@ export class CircularHeatmapComponent implements OnInit {
@ViewChildren(MatChip) chips!: QueryList;
matChipsArray: MatChip[] = [];
+ displayMessage(dialogInfo: DialogInfo) {
+ // Remove focus from the button that becomes aria unavailable (avoids ugly console error message)
+ const buttonElement = document.activeElement as HTMLElement;
+ buttonElement.blur();
+
+ this.modal.openDialog(dialogInfo);
+ }
+
private LoadMaturityDataFromGeneratedYaml() {
return new Promise((resolve, reject) => {
console.log(`${this.perfNow()}s: LoadMaturityData Fetch`);
@@ -843,9 +855,26 @@ export class CircularHeatmapComponent implements OnInit {
this.noActivitytoGrey();
}
- ResetIsImplemented() {
- localStorage.removeItem('dataset');
- this.loadDataset();
+ deleteLocalTeamsProgress() {
+ // Remove focus from the button that becomes aria unavailable (avoids ugly console error message)
+ const buttonElement = document.activeElement as HTMLElement;
+ buttonElement.blur();
+
+ let title: string = 'Delete local browser data';
+ let message: string =
+ 'Do you want to delete all progress for each team?' +
+ '\n\nThis deletes all progress stored in your local browser, but does ' +
+ 'not change any progress stored in the yaml file on the server.';
+ let buttons: string[] = ['Cancel', 'Delete'];
+ this.modal
+ .openDialog({ title, message, buttons, template: '' })
+ .afterClosed()
+ .subscribe(data => {
+ if (data === 'Delete') {
+ localStorage.removeItem('dataset');
+ location.reload(); // Make sure all load routines are initialized
+ }
+ });
}
saveDataset() {
diff --git a/src/app/component/modal-message/modal-message.component.css b/src/app/component/modal-message/modal-message.component.css
new file mode 100644
index 000000000..375caf548
--- /dev/null
+++ b/src/app/component/modal-message/modal-message.component.css
@@ -0,0 +1,14 @@
+.dialog {
+ margin: 0.5em;
+ padding: 1em;
+}
+
+.dialog-buttons {
+ display: flex;
+ justify-content: flex-end;
+}
+
+button {
+ min-width: 5rem;
+ margin: 0 1rem;
+}
\ No newline at end of file
diff --git a/src/app/component/modal-message/modal-message.component.html b/src/app/component/modal-message/modal-message.component.html
new file mode 100644
index 000000000..f1ff005f7
--- /dev/null
+++ b/src/app/component/modal-message/modal-message.component.html
@@ -0,0 +1,14 @@
+
+
+
+
diff --git a/src/app/component/modal-message/modal-message.component.spec.ts b/src/app/component/modal-message/modal-message.component.spec.ts
new file mode 100644
index 000000000..213ea3cb9
--- /dev/null
+++ b/src/app/component/modal-message/modal-message.component.spec.ts
@@ -0,0 +1,52 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { DialogInfo, ModalMessageComponent } from './modal-message.component';
+import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { MatDialogModule } from '@angular/material/dialog';
+
+describe('ModalMessageComponent', () => {
+ let component: ModalMessageComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [NoopAnimationsModule, MatDialogModule],
+ declarations: [ModalMessageComponent],
+ providers: [
+ { provide: MatDialogRef, useValue: {} },
+ { provide: MAT_DIALOG_DATA, useValue: {} },
+ ],
+ }).compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(ModalMessageComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('should render markdown correctly in the dialog', () => {
+ const dialogInfo: DialogInfo = new DialogInfo('A **test** markdown.');
+ const dialogRef: MatDialogRef =
+ component.openDialog(dialogInfo);
+
+ expect(dialogRef.componentInstance.data.message).toContain(
+ 'test'
+ );
+ });
+
+ it('should render markdown correctly in the dialog', () => {
+ const dialogInfo: DialogInfo = new DialogInfo('A **test** markdown.');
+ const dialogRef: MatDialogRef =
+ component.openDialog(dialogInfo);
+
+ // Check if markdown rendering is applied
+ expect(dialogRef.componentInstance.data.message).toContain(
+ 'test'
+ );
+ });
+});
diff --git a/src/app/component/modal-message/modal-message.component.ts b/src/app/component/modal-message/modal-message.component.ts
new file mode 100644
index 000000000..2dbb00469
--- /dev/null
+++ b/src/app/component/modal-message/modal-message.component.ts
@@ -0,0 +1,87 @@
+import { Inject, Component, OnInit } from '@angular/core';
+import {
+ MAT_DIALOG_DATA,
+ MatDialogRef,
+ MatDialog,
+ MatDialogConfig,
+} from '@angular/material/dialog';
+import * as md from 'markdown-it';
+
+@Component({
+ selector: 'app-modal-message',
+ templateUrl: './modal-message.component.html',
+ styleUrls: ['./modal-message.component.css'],
+})
+export class ModalMessageComponent implements OnInit {
+ data: DialogInfo;
+ markdown: md = md();
+
+ DSOMM_host: string = 'https://github.com/devsecopsmaturitymodel';
+ DSOMM_url: string = `${this.DSOMM_host}/DevSecOps-MaturityModel-data`;
+ meassageTemplates: Record = {
+ generated_yaml: new DialogInfo(
+ `{message}\n\n` +
+ `Please download the activity template \`generated.yaml\` ` +
+ `from [DSOMM-data](${this.DSOMM_url}) on GitHub.\n\n` +
+ 'The DSOMM activities are maintained and distributed ' +
+ 'separately from the software.',
+ 'DSOMM startup problems'
+ ),
+ };
+
+ constructor(
+ public dialog: MatDialog,
+ public dialogRef: MatDialogRef,
+ @Inject(MAT_DIALOG_DATA) data: DialogInfo
+ ) {
+ this.data = data;
+ }
+
+ // eslint-disable-next-line @angular-eslint/no-empty-lifecycle-method
+ ngOnInit(): void {}
+
+ openDialog(
+ dialogInfo: DialogInfo | string
+ ): MatDialogRef {
+ if (typeof dialogInfo === 'string') {
+ dialogInfo = new DialogInfo(dialogInfo);
+ }
+ if (
+ dialogInfo.template &&
+ this.meassageTemplates.hasOwnProperty(dialogInfo.template)
+ ) {
+ let template: DialogInfo = this.meassageTemplates[dialogInfo.template];
+ dialogInfo.title = dialogInfo.title || template?.title;
+ dialogInfo.message = template?.message?.replace(
+ '{message}',
+ dialogInfo.message
+ );
+ }
+
+ dialogInfo.message = this.markdown.render(dialogInfo.message);
+
+ const dialogConfig = new MatDialogConfig();
+ dialogConfig.id = 'modal-message';
+ dialogConfig.disableClose = true;
+ dialogConfig.data = dialogInfo;
+ dialogConfig.autoFocus = false;
+ this.dialogRef = this.dialog.open(ModalMessageComponent, dialogConfig);
+ return this.dialogRef;
+ }
+
+ closeDialog(buttonName: string) {
+ this.dialogRef?.close(buttonName);
+ }
+}
+
+export class DialogInfo {
+ title: string = '';
+ template: string | null = '';
+ message: string = '';
+ buttons: string[] = ['OK'];
+
+ constructor(msg: string = '', title: string = '') {
+ this.message = msg;
+ this.title = title;
+ }
+}