Skip to content

Commit 83b0ab3

Browse files
authored
Merge pull request #355 from vbakke/fix/334-warning-b4-delete-data
Add warning before deleting browser progress
2 parents dacdbd8 + 1db756e commit 83b0ab3

File tree

8 files changed

+225
-17
lines changed

8 files changed

+225
-17
lines changed

src/app/app.module.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ import { AboutUsComponent } from './component/about-us/about-us.component';
2222
import { DependencyGraphComponent } from './component/dependency-graph/dependency-graph.component';
2323
import { TeamsComponent } from './component/teams/teams.component';
2424
import { ToStringValuePipe } from './pipe/to-string-value.pipe';
25+
import { ModalMessageComponent } from './component/modal-message/modal-message.component';
26+
import {
27+
MatDialogModule,
28+
MAT_DIALOG_DATA,
29+
MatDialogRef,
30+
} from '@angular/material/dialog';
2531

2632
@NgModule({
2733
declarations: [
@@ -40,16 +46,23 @@ import { ToStringValuePipe } from './pipe/to-string-value.pipe';
4046
TeamsComponent,
4147
ToStringValuePipe,
4248
UserdayComponent,
49+
ModalMessageComponent,
4350
],
4451
imports: [
4552
BrowserModule,
4653
AppRoutingModule,
4754
BrowserAnimationsModule,
4855
MaterialModule,
56+
MatDialogModule,
4957
ReactiveFormsModule,
5058
HttpClientModule,
5159
],
52-
providers: [ymlService],
60+
providers: [
61+
ymlService,
62+
ModalMessageComponent,
63+
{ provide: MAT_DIALOG_DATA, useValue: {} },
64+
{ provide: MatDialogRef, useValue: { close: (dialogResult: any) => {} } },
65+
],
5366
bootstrap: [AppComponent],
5467
})
5568
export class AppModule {}

src/app/component/circular-heatmap/circular-heatmap.component.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,8 +284,8 @@ <h2>Nothing to show</h2>
284284
class="normal-button"
285285
mat-raised-button
286286
class="resetButtonClass"
287-
(click)="ResetIsImplemented()">
288-
Reset Implemented
287+
(click)="deleteLocalTeamsProgress()">
288+
Delete team progress
289289
</button>
290290
</div>
291291
</div>

src/app/component/circular-heatmap/circular-heatmap.component.spec.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,25 @@ import { ymlService } from 'src/app/service/yaml-parser/yaml-parser.service';
44
import { CircularHeatmapComponent } from './circular-heatmap.component';
55
import { RouterTestingModule } from '@angular/router/testing';
66
import { MatChip } from '@angular/material/chips';
7+
import { ModalMessageComponent } from '../modal-message/modal-message.component';
78

89
describe('CircularHeatmapComponent', () => {
910
let component: CircularHeatmapComponent;
1011
let fixture: ComponentFixture<CircularHeatmapComponent>;
1112

1213
beforeEach(async () => {
1314
await TestBed.configureTestingModule({
14-
providers: [ymlService, HttpClient, HttpHandler],
15+
declarations: [CircularHeatmapComponent, MatChip],
1516
imports: [RouterTestingModule],
16-
declarations: [CircularHeatmapComponent],
17+
providers: [
18+
ymlService,
19+
HttpClient,
20+
HttpHandler,
21+
{ provide: ModalMessageComponent, useValue: {} },
22+
],
1723
}).compileComponents();
18-
});
19-
beforeEach(async () => {
20-
TestBed.configureTestingModule({
21-
declarations: [MatChip],
22-
}).compileComponents();
23-
});
2424

25-
beforeEach(() => {
26-
fixture = TestBed.createComponent(CircularHeatmapComponent);
25+
fixture = TestBed.createComponent(CircularHeatmapComponent); // Create fixture and component here
2726
component = fixture.componentInstance;
2827
fixture.detectChanges();
2928
});

src/app/component/circular-heatmap/circular-heatmap.component.ts

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ import * as yaml from 'js-yaml';
1111
import { Router } from '@angular/router';
1212
import { MatChip } from '@angular/material/chips';
1313
import * as md from 'markdown-it';
14+
import {
15+
ModalMessageComponent,
16+
DialogInfo,
17+
} from '../modal-message/modal-message.component';
1418

1519
export interface activitySchema {
1620
uuid: string;
@@ -62,7 +66,7 @@ export class CircularHeatmapComponent implements OnInit {
6266
constructor(
6367
private yaml: ymlService,
6468
private router: Router,
65-
private changeDetector: ChangeDetectorRef
69+
public modal: ModalMessageComponent
6670
) {
6771
this.showOverlay = false;
6872
}
@@ -83,6 +87,14 @@ export class CircularHeatmapComponent implements OnInit {
8387
@ViewChildren(MatChip) chips!: QueryList<MatChip>;
8488
matChipsArray: MatChip[] = [];
8589

90+
displayMessage(dialogInfo: DialogInfo) {
91+
// Remove focus from the button that becomes aria unavailable (avoids ugly console error message)
92+
const buttonElement = document.activeElement as HTMLElement;
93+
buttonElement.blur();
94+
95+
this.modal.openDialog(dialogInfo);
96+
}
97+
8698
private LoadMaturityDataFromGeneratedYaml() {
8799
return new Promise<void>((resolve, reject) => {
88100
console.log(`${this.perfNow()}s: LoadMaturityData Fetch`);
@@ -843,9 +855,26 @@ export class CircularHeatmapComponent implements OnInit {
843855
this.noActivitytoGrey();
844856
}
845857

846-
ResetIsImplemented() {
847-
localStorage.removeItem('dataset');
848-
this.loadDataset();
858+
deleteLocalTeamsProgress() {
859+
// Remove focus from the button that becomes aria unavailable (avoids ugly console error message)
860+
const buttonElement = document.activeElement as HTMLElement;
861+
buttonElement.blur();
862+
863+
let title: string = 'Delete local browser data';
864+
let message: string =
865+
'Do you want to delete all progress for each team?' +
866+
'\n\nThis deletes all progress stored in your local browser, but does ' +
867+
'not change any progress stored in the yaml file on the server.';
868+
let buttons: string[] = ['Cancel', 'Delete'];
869+
this.modal
870+
.openDialog({ title, message, buttons, template: '' })
871+
.afterClosed()
872+
.subscribe(data => {
873+
if (data === 'Delete') {
874+
localStorage.removeItem('dataset');
875+
location.reload(); // Make sure all load routines are initialized
876+
}
877+
});
849878
}
850879

851880
saveDataset() {
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
.dialog {
2+
margin: 0.5em;
3+
padding: 1em;
4+
}
5+
6+
.dialog-buttons {
7+
display: flex;
8+
justify-content: flex-end;
9+
}
10+
11+
button {
12+
min-width: 5rem;
13+
margin: 0 1rem;
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<div mat-dialog-content class="dialog">
2+
<h2 mat-dialog-title>{{ data.title }}</h2>
3+
<p [innerHTML]="data.message"></p>
4+
</div>
5+
<div mat-dialog-actions class="dialog-buttons">
6+
<button
7+
*ngFor="let name of data.buttons"
8+
mat-button
9+
mat-dialog-close
10+
class="mat-focus-indicator downloadButtonClass mat-raised-button mat-button-base"
11+
(click)="closeDialog(name)">
12+
{{ name }}
13+
</button>
14+
</div>
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
import { DialogInfo, ModalMessageComponent } from './modal-message.component';
3+
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
4+
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
5+
import { MatDialogModule } from '@angular/material/dialog';
6+
7+
describe('ModalMessageComponent', () => {
8+
let component: ModalMessageComponent;
9+
let fixture: ComponentFixture<ModalMessageComponent>;
10+
11+
beforeEach(async () => {
12+
await TestBed.configureTestingModule({
13+
imports: [NoopAnimationsModule, MatDialogModule],
14+
declarations: [ModalMessageComponent],
15+
providers: [
16+
{ provide: MatDialogRef, useValue: {} },
17+
{ provide: MAT_DIALOG_DATA, useValue: {} },
18+
],
19+
}).compileComponents();
20+
});
21+
22+
beforeEach(() => {
23+
fixture = TestBed.createComponent(ModalMessageComponent);
24+
component = fixture.componentInstance;
25+
fixture.detectChanges();
26+
});
27+
28+
it('should create', () => {
29+
expect(component).toBeTruthy();
30+
});
31+
32+
it('should render markdown correctly in the dialog', () => {
33+
const dialogInfo: DialogInfo = new DialogInfo('A **test** markdown.');
34+
const dialogRef: MatDialogRef<ModalMessageComponent> =
35+
component.openDialog(dialogInfo);
36+
37+
expect(dialogRef.componentInstance.data.message).toContain(
38+
'<strong>test</strong>'
39+
);
40+
});
41+
42+
it('should render markdown correctly in the dialog', () => {
43+
const dialogInfo: DialogInfo = new DialogInfo('A **test** markdown.');
44+
const dialogRef: MatDialogRef<ModalMessageComponent> =
45+
component.openDialog(dialogInfo);
46+
47+
// Check if markdown rendering is applied
48+
expect(dialogRef.componentInstance.data.message).toContain(
49+
'<strong>test</strong>'
50+
);
51+
});
52+
});
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { Inject, Component, OnInit } from '@angular/core';
2+
import {
3+
MAT_DIALOG_DATA,
4+
MatDialogRef,
5+
MatDialog,
6+
MatDialogConfig,
7+
} from '@angular/material/dialog';
8+
import * as md from 'markdown-it';
9+
10+
@Component({
11+
selector: 'app-modal-message',
12+
templateUrl: './modal-message.component.html',
13+
styleUrls: ['./modal-message.component.css'],
14+
})
15+
export class ModalMessageComponent implements OnInit {
16+
data: DialogInfo;
17+
markdown: md = md();
18+
19+
DSOMM_host: string = 'https://github.com/devsecopsmaturitymodel';
20+
DSOMM_url: string = `${this.DSOMM_host}/DevSecOps-MaturityModel-data`;
21+
meassageTemplates: Record<string, DialogInfo> = {
22+
generated_yaml: new DialogInfo(
23+
`{message}\n\n` +
24+
`Please download the activity template \`generated.yaml\` ` +
25+
`from [DSOMM-data](${this.DSOMM_url}) on GitHub.\n\n` +
26+
'The DSOMM activities are maintained and distributed ' +
27+
'separately from the software.',
28+
'DSOMM startup problems'
29+
),
30+
};
31+
32+
constructor(
33+
public dialog: MatDialog,
34+
public dialogRef: MatDialogRef<ModalMessageComponent>,
35+
@Inject(MAT_DIALOG_DATA) data: DialogInfo
36+
) {
37+
this.data = data;
38+
}
39+
40+
// eslint-disable-next-line @angular-eslint/no-empty-lifecycle-method
41+
ngOnInit(): void {}
42+
43+
openDialog(
44+
dialogInfo: DialogInfo | string
45+
): MatDialogRef<ModalMessageComponent> {
46+
if (typeof dialogInfo === 'string') {
47+
dialogInfo = new DialogInfo(dialogInfo);
48+
}
49+
if (
50+
dialogInfo.template &&
51+
this.meassageTemplates.hasOwnProperty(dialogInfo.template)
52+
) {
53+
let template: DialogInfo = this.meassageTemplates[dialogInfo.template];
54+
dialogInfo.title = dialogInfo.title || template?.title;
55+
dialogInfo.message = template?.message?.replace(
56+
'{message}',
57+
dialogInfo.message
58+
);
59+
}
60+
61+
dialogInfo.message = this.markdown.render(dialogInfo.message);
62+
63+
const dialogConfig = new MatDialogConfig();
64+
dialogConfig.id = 'modal-message';
65+
dialogConfig.disableClose = true;
66+
dialogConfig.data = dialogInfo;
67+
dialogConfig.autoFocus = false;
68+
this.dialogRef = this.dialog.open(ModalMessageComponent, dialogConfig);
69+
return this.dialogRef;
70+
}
71+
72+
closeDialog(buttonName: string) {
73+
this.dialogRef?.close(buttonName);
74+
}
75+
}
76+
77+
export class DialogInfo {
78+
title: string = '';
79+
template: string | null = '';
80+
message: string = '';
81+
buttons: string[] = ['OK'];
82+
83+
constructor(msg: string = '', title: string = '') {
84+
this.message = msg;
85+
this.title = title;
86+
}
87+
}

0 commit comments

Comments
 (0)