Skip to content

Commit b9a0d24

Browse files
rfontanarosagino-m
andauthored
Adds "Invalid Survey" dialog instead of disabling "Publish changes" button (#2158)
Co-authored-by: Gino Miceli <[email protected]>
1 parent 8adc887 commit b9a0d24

File tree

5 files changed

+233
-88
lines changed

5 files changed

+233
-88
lines changed

web/src/app/components/header/header.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
<button
5555
mat-flat-button
5656
class="finish-edit-button"
57-
[disabled]="isPublishingChanges || !isDraftSurveyDirtyAndValid()"
57+
[disabled]="isPublishingChanges"
5858
(click)="onFinishEditSurveyClick()"
5959
>
6060
Publish changes

web/src/app/components/header/header.component.ts

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -102,19 +102,28 @@ export class HeaderComponent {
102102
}
103103

104104
async onFinishEditSurveyClick() {
105-
this.isPublishingChanges = true;
106-
await this.draftSurveyService.updateSurvey();
107-
this.isPublishingChanges = false;
108-
this.navigationService.selectSurvey(this.surveyId);
105+
if (this.isDraftSurveyValid()) {
106+
this.isPublishingChanges = true;
107+
await this.draftSurveyService.updateSurvey();
108+
this.isPublishingChanges = false;
109+
this.navigationService.selectSurvey(this.surveyId);
110+
return;
111+
}
112+
113+
this.dialog.open(JobDialogComponent, {
114+
data: {dialogType: DialogType.InvalidSurvey},
115+
panelClass: 'small-width-dialog',
116+
});
109117
}
110118

111-
isDraftSurveyDirtyAndValid() {
112-
return (
113-
this.draftSurveyService.dirty &&
114-
this.draftSurveyService.valid.reduce(
115-
(accumulator, currentValue) => accumulator && currentValue,
116-
true
117-
)
119+
isDraftSurveyValid(): boolean {
120+
return this.draftSurveyService.valid.reduce(
121+
(accumulator, currentValue) => accumulator && currentValue,
122+
true
118123
);
119124
}
125+
126+
isDraftSurveyDirtyAndValid(): boolean {
127+
return this.draftSurveyService.dirty && this.isDraftSurveyValid();
128+
}
120129
}

web/src/app/pages/edit-survey/job-dialog/job-dialog.component.html

Lines changed: 7 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -17,62 +17,32 @@
1717
<h1 mat-dialog-title>{{ title }}</h1>
1818

1919
<mat-dialog-content [ngSwitch]="data.dialogType">
20-
<mat-form-field
21-
*ngSwitchCase="[DialogType.AddJob, DialogType.RenameJob].includes(data.dialogType)
22-
? data.dialogType
23-
: ''
24-
">
20+
<p *ngIf="content">{{ content }}</p>
21+
22+
<mat-form-field *ngIf="[DialogType.AddJob, DialogType.RenameJob].includes(data.dialogType)">
2523
<mat-label>Job name</mat-label>
2624

2725
<input [id]="jobNameFieldId" matInput [(ngModel)]="data.jobName" />
2826
</mat-form-field>
29-
30-
<p *ngSwitchCase="DialogType.DeleteJob">
31-
This job and all of its associated data will be deleted. This operation
32-
can’t be undone. Are you sure?
33-
</p>
34-
35-
<p *ngSwitchCase="DialogType.UndoJobs">
36-
If you leave this page, changes you’ve made to this survey won’t be
37-
published. Are you sure you want to continue?
38-
</p>
39-
40-
<p *ngSwitchCase="DialogType.DeleteLois">
41-
All predefined data collection sites and their associated data will
42-
be immediately deleted. This action cannot be undone.
43-
</p>
44-
45-
<p *ngSwitchCase="DialogType.DeleteOption">
46-
Are you sure you wish to delete this option?
47-
All associated data will be lost. This cannot be undone.
48-
</p>
49-
50-
<p *ngSwitchCase="DialogType.DeleteSurvey">
51-
Are you sure you wish to delete this survey?
52-
All associated data will be lost. This cannot be undone.
53-
</p>
54-
55-
<p *ngSwitchCase="DialogType.DisableFreeForm">
56-
Data collector will no longer be able to add new sites for this job.
57-
Data will only be collected for existing sites.
58-
</p>
5927
</mat-dialog-content>
6028

6129
<mat-dialog-actions>
6230
<button
6331
mat-button
6432
color="primary"
6533
mat-dialog-close
34+
*ngIf="backButtonLabel"
6635
>
67-
Cancel
36+
{{ backButtonLabel }}
6837
</button>
6938

7039
<button
7140
mat-button
7241
color="primary"
7342
[mat-dialog-close]="data"
7443
cdkFocusInitial
44+
*ngIf="continueButtonLabel"
7545
>
76-
{{ buttonLabel }}
46+
{{ continueButtonLabel }}
7747
</button>
7848
</mat-dialog-actions>
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/**
2+
* Copyright 2025 The Ground Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the 'License');
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an 'AS IS' BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import {
18+
ComponentFixture,
19+
TestBed,
20+
fakeAsync,
21+
tick,
22+
} from '@angular/core/testing';
23+
import {
24+
MAT_DIALOG_DATA,
25+
MatDialogModule,
26+
MatDialogRef,
27+
} from '@angular/material/dialog';
28+
import {By} from '@angular/platform-browser';
29+
30+
import {
31+
DialogData,
32+
DialogType,
33+
JobDialogComponent,
34+
dialogConfigs,
35+
} from './job-dialog.component';
36+
37+
describe('JobDialogComponent', () => {
38+
let component: JobDialogComponent;
39+
let fixture: ComponentFixture<JobDialogComponent>;
40+
let dialogRefSpy: jasmine.SpyObj<MatDialogRef<JobDialogComponent>>;
41+
42+
const mockDialogData: DialogData = {
43+
dialogType: DialogType.UndoJobs,
44+
};
45+
46+
beforeEach(async () => {
47+
dialogRefSpy = jasmine.createSpyObj('MatDialogRef', ['close']);
48+
49+
await TestBed.configureTestingModule({
50+
declarations: [JobDialogComponent],
51+
imports: [MatDialogModule],
52+
providers: [
53+
{provide: MatDialogRef, useValue: dialogRefSpy},
54+
{provide: MAT_DIALOG_DATA, useValue: mockDialogData},
55+
],
56+
}).compileComponents();
57+
});
58+
59+
beforeEach(() => {
60+
fixture = TestBed.createComponent(JobDialogComponent);
61+
component = fixture.componentInstance;
62+
fixture.detectChanges();
63+
});
64+
65+
it('should create', () => {
66+
expect(component).toBeTruthy();
67+
});
68+
69+
it('should close dialog when back button is clicked', () => {
70+
const backButton = fixture.debugElement.query(
71+
By.css('.mat-mdc-dialog-actions button:first-child')
72+
);
73+
backButton.nativeElement.click();
74+
expect(dialogRefSpy.close).toHaveBeenCalled();
75+
});
76+
77+
it('should close dialog when continue button is clicked', () => {
78+
const continueButton = fixture.debugElement.query(
79+
By.css('.mat-mdc-dialog-actions button:last-child')
80+
);
81+
continueButton.nativeElement.click();
82+
expect(dialogRefSpy.close).toHaveBeenCalled();
83+
});
84+
85+
it('should display the correct title in the template', () => {
86+
const titleElement = fixture.debugElement.query(
87+
By.css('.mat-mdc-dialog-title')
88+
);
89+
expect(titleElement.nativeElement.textContent).toContain(
90+
dialogConfigs[DialogType.UndoJobs].title
91+
);
92+
});
93+
94+
it('should display the correct content in the template', () => {
95+
const contentElement = fixture.debugElement.query(
96+
By.css('.mat-mdc-dialog-content')
97+
);
98+
expect(contentElement.nativeElement.textContent).toContain(
99+
dialogConfigs[DialogType.UndoJobs].content
100+
);
101+
});
102+
103+
it('should display the back button with the correct label', () => {
104+
const backButton = fixture.debugElement.query(
105+
By.css('.mat-mdc-dialog-actions button:first-child')
106+
);
107+
expect(backButton.nativeElement.textContent).toContain(
108+
dialogConfigs[DialogType.UndoJobs].backButtonLabel
109+
);
110+
});
111+
112+
it('should display the continue button with the correct label', () => {
113+
const continueButton = fixture.debugElement.query(
114+
By.css('.mat-mdc-dialog-actions button:last-child')
115+
);
116+
expect(continueButton.nativeElement.textContent).toContain(
117+
dialogConfigs[DialogType.UndoJobs].continueButtonLabel
118+
);
119+
});
120+
});

web/src/app/pages/edit-survey/job-dialog/job-dialog.component.ts

Lines changed: 85 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,79 @@ export enum DialogType {
2626
DeleteOption,
2727
DeleteSurvey,
2828
DisableFreeForm,
29+
InvalidSurvey,
2930
}
3031

32+
export interface DialogConfig {
33+
title: string;
34+
content?: string;
35+
backButtonLabel?: string;
36+
continueButtonLabel?: string;
37+
}
38+
39+
export const dialogConfigs: Record<DialogType, DialogConfig> = {
40+
[DialogType.AddJob]: {
41+
title: 'Add new job',
42+
backButtonLabel: 'Cancel',
43+
continueButtonLabel: 'Create',
44+
},
45+
[DialogType.RenameJob]: {
46+
title: 'Rename job',
47+
backButtonLabel: 'Cancel',
48+
continueButtonLabel: 'Rename',
49+
},
50+
[DialogType.UndoJobs]: {
51+
title: 'Unpublished changes',
52+
content:
53+
'If you leave this page, changes you’ve made to this survey won’t be published. Are you sure you want to continue?',
54+
backButtonLabel: 'Go back',
55+
continueButtonLabel: 'Continue',
56+
},
57+
[DialogType.DeleteJob]: {
58+
title: 'Delete job',
59+
content:
60+
'This job and all of its associated data will be deleted. This operation can’t be undone. Are you sure?',
61+
backButtonLabel: 'Cancel',
62+
continueButtonLabel: 'Confirm',
63+
},
64+
[DialogType.DeleteLois]: {
65+
title: 'Delete predefined sites',
66+
content:
67+
'All predefined data collection sites and their associated data will be immediately deleted. This action cannot be undone.',
68+
backButtonLabel: 'Cancel',
69+
continueButtonLabel: 'Confirm',
70+
},
71+
[DialogType.DeleteOption]: {
72+
title: 'Delete option',
73+
content:
74+
'Are you sure you wish to delete this option? All associated data will be lost. This cannot be undone.',
75+
backButtonLabel: 'Cancel',
76+
continueButtonLabel: 'Confirm',
77+
},
78+
[DialogType.DeleteSurvey]: {
79+
title: 'Delete survey',
80+
content:
81+
'Are you sure you wish to delete this survey? All associated data will be lost. This cannot be undone.',
82+
backButtonLabel: 'Cancel',
83+
continueButtonLabel: 'Confirm',
84+
},
85+
[DialogType.DisableFreeForm]: {
86+
title: 'Disable free-form data collection?',
87+
content:
88+
'Data collector will no longer be able to add new sites for this job. Data will only be collected for existing sites.',
89+
backButtonLabel: 'Cancel',
90+
continueButtonLabel: 'Confirm',
91+
},
92+
[DialogType.InvalidSurvey]: {
93+
title: 'Fix issues with survey',
94+
content: 'To publish changes, fix any outstanding issues with your survey.',
95+
backButtonLabel: 'Go back',
96+
},
97+
};
98+
3199
export interface DialogData {
32100
dialogType: DialogType;
33-
jobName: string;
101+
jobName?: string;
34102
}
35103

36104
@Component({
@@ -48,46 +116,24 @@ export class JobDialogComponent {
48116
@Inject(MAT_DIALOG_DATA) public data: DialogData
49117
) {}
50118

51-
public get title() {
52-
switch (this.data.dialogType) {
53-
case DialogType.AddJob:
54-
return 'Add new job';
55-
case DialogType.RenameJob:
56-
return 'Rename job';
57-
case DialogType.UndoJobs:
58-
return 'Unpublished changes';
59-
case DialogType.DeleteJob:
60-
return 'Delete job';
61-
case DialogType.DeleteLois:
62-
return 'Delete predefined sites';
63-
case DialogType.DeleteOption:
64-
return 'Delete option';
65-
case DialogType.DeleteSurvey:
66-
return 'Delete survey';
67-
case DialogType.DisableFreeForm:
68-
return 'Disable free-form data collection?';
69-
default:
70-
return '';
71-
}
119+
get dialogConfig(): DialogConfig {
120+
return dialogConfigs[this.data.dialogType];
121+
}
122+
123+
get title(): string {
124+
return this.dialogConfig.title;
125+
}
126+
127+
get content(): string | undefined {
128+
return this.dialogConfig.content;
129+
}
130+
131+
get backButtonLabel(): string | undefined {
132+
return this.dialogConfig.backButtonLabel;
72133
}
73134

74-
public get buttonLabel() {
75-
switch (this.data.dialogType) {
76-
case DialogType.AddJob:
77-
return 'Create';
78-
case DialogType.RenameJob:
79-
return 'Rename';
80-
case DialogType.UndoJobs:
81-
return 'Continue';
82-
case DialogType.DeleteJob:
83-
case DialogType.DeleteLois:
84-
case DialogType.DeleteOption:
85-
case DialogType.DeleteSurvey:
86-
case DialogType.DisableFreeForm:
87-
return 'Confirm';
88-
default:
89-
return '';
90-
}
135+
get continueButtonLabel(): string | undefined {
136+
return this.dialogConfig.continueButtonLabel;
91137
}
92138

93139
get jobNameFieldId() {

0 commit comments

Comments
 (0)