Skip to content

Commit 95d6ec8

Browse files
authored
EUI-1783: Support for deleting hidden field values (#591)
* EUI-1783: Support for deleting hidden field values Ensure any existing value of a hidden field is deleted in the CCD backend, unless the field has the `retain_hidden_value` flag set to `true` (see RDM-9024), in which case the existing value will remain untouched. * Refactor `fg` parameter to `formGroup` for clarity, following suggestion from @rayliangatos
1 parent f0795a1 commit 95d6ec8

File tree

8 files changed

+214
-28
lines changed

8 files changed

+214
-28
lines changed

RELEASE-NOTES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
## RELEASE NOTES
2+
### Version 2.64.42-retain-hidden-value-support
3+
**EUI-1783** Delete hidden field value except if `retain_hidden_value` flag is true
4+
25
### Version 2.64.41-reinstate-EUI-2575
36
**EUI-2657** - Reinstate EUI-2575 (`use_case` query param), in line with the reversion in CCD Demo environment being undone
47

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@hmcts/ccd-case-ui-toolkit",
3-
"version": "2.64.41-reinstate-EUI-2575",
3+
"version": "2.64.42-retain-hidden-value-support",
44
"engines": {
55
"yarn": "^1.12.3",
66
"npm": "^5.6.0"

src/shared/components/case-editor/case-edit-submit/case-edit-submit.component.spec.ts

Lines changed: 137 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { ActivatedRoute } from '@angular/router';
44
import { of, Observable, BehaviorSubject } from 'rxjs';
55
import { FormControl, FormGroup } from '@angular/forms';
66
import { By } from '@angular/platform-browser';
7+
import createSpy = jasmine.createSpy;
78
import createSpyObj = jasmine.createSpyObj;
89
import { FieldsUtils } from '../../../services/fields/fields.utils';
910
import { CaseReferencePipe } from '../../../pipes/case-reference/case-reference.pipe';
@@ -22,9 +23,11 @@ import { CaseEditSubmitComponent } from './case-edit-submit.component';
2223
import { CaseEditComponent } from '../case-edit/case-edit.component';
2324
import { CaseEditPageComponent } from '../case-edit-page/case-edit-page.component';
2425
import { ProfileService, ProfileNotifier } from '../../../services/profile';
25-
import { Profile } from '../../../domain';
26+
import { CaseEventData, Profile } from '../../../domain';
2627
import { createAProfile } from '../../../domain/profile/profile.test.fixture';
2728
import { text } from '../../../test/helpers';
29+
import { FieldsPurger } from '../../../services';
30+
import { WizardPageField } from '../domain';
2831

2932
describe('CaseEditSubmitComponent', () => {
3033

@@ -40,6 +43,13 @@ describe('CaseEditSubmitComponent', () => {
4043
const FORM_GROUP = new FormGroup({
4144
'data': new FormGroup({ 'PersonLastName': new FormControl('Khaleesi') })
4245
});
46+
const FORM_GROUP_WITH_HIDDEN_FIELDS = new FormGroup({
47+
'data': new FormGroup({
48+
'field1': new FormControl({ value: 'Hidden value to be retained', disabled: true }),
49+
'field2': new FormControl({ value: 'Hidden value not to be retained', disabled: true }),
50+
'field3': new FormControl('Hide both')
51+
})
52+
});
4353
let caseEditComponent: any;
4454
let pages: WizardPage[];
4555
let wizard: Wizard;
@@ -51,6 +61,7 @@ describe('CaseEditSubmitComponent', () => {
5161
let caseField1: CaseField = aCaseField('field1', 'field1', 'Text', 'OPTIONAL', 3);
5262
let caseField2: CaseField = aCaseField('field2', 'field2', 'Text', 'OPTIONAL', 2);
5363
let caseField3: CaseField = aCaseField('field3', 'field3', 'Text', 'OPTIONAL', 1);
64+
let caseFieldRetainHiddenValue: CaseField = aCaseField('field1', 'field1', 'Text', 'OPTIONAL', 3, null, true);
5465
const $EVENT_NOTES = By.css('#fieldset-event');
5566
let cancelled: any;
5667
let snapshot: any;
@@ -692,4 +703,129 @@ describe('CaseEditSubmitComponent', () => {
692703
expect(error).toBeFalsy();
693704
});
694705
});
706+
707+
describe('Form submit test', () => {
708+
pages = [
709+
aWizardPage('page1', 'Page 1', 1),
710+
];
711+
const firstPage = pages[0];
712+
caseFieldRetainHiddenValue.show_condition = 'field3!="Hide both"';
713+
caseField2.show_condition = 'field3!="Hide both"';
714+
const WP_FIELD_1: WizardPageField = { case_field_id: caseFieldRetainHiddenValue.id };
715+
const WP_FIELD_2: WizardPageField = { case_field_id: caseField2.id };
716+
const WP_FIELD_3: WizardPageField = { case_field_id: caseField3.id };
717+
firstPage.wizard_page_fields = [WP_FIELD_1, WP_FIELD_2, WP_FIELD_3];
718+
wizard = new Wizard(pages);
719+
const queryParamMapNoProfile = createSpyObj('queryParamMap', ['get']);
720+
const snapshotNoProfile = {
721+
pathFromRoot: [
722+
{},
723+
{
724+
data: {
725+
nonProfileData: {
726+
user: {
727+
idam: {
728+
id: 'userId',
729+
email: 'string',
730+
forename: 'string',
731+
surname: 'string',
732+
roles: ['caseworker', 'caseworker-test', 'caseworker-probate-solicitor']
733+
}
734+
},
735+
'isSolicitor': () => false,
736+
}
737+
}
738+
}
739+
],
740+
queryParamMap: queryParamMapNoProfile,
741+
};
742+
let PROFILE_OBS: Observable<Profile> = Observable.of(PROFILE);
743+
const mockRouteNoProfile = {
744+
params: of({id: 123}),
745+
snapshot: snapshotNoProfile
746+
};
747+
let caseEditComponentSubmitSpy: any;
748+
749+
beforeEach(async(() => {
750+
// Need to set the page case_fields here because, for some reason, if set initially then these get overridden
751+
// by some unknown default!
752+
firstPage.case_fields = [caseFieldRetainHiddenValue, caseField2, caseField3];
753+
orderService = new OrderService();
754+
casesReferencePipe = createSpyObj<CaseReferencePipe>('caseReference', ['transform']);
755+
cancelled = createSpyObj('cancelled', ['emit'])
756+
caseEditComponent = {
757+
'form': FORM_GROUP_WITH_HIDDEN_FIELDS,
758+
'fieldsPurger': new FieldsPurger(fieldsUtils),
759+
'data': '',
760+
'eventTrigger': { 'case_fields': [caseFieldRetainHiddenValue, caseField2, caseField3], 'can_save_draft': true },
761+
'wizard': wizard,
762+
'hasPrevious': () => true,
763+
'getPage': () => firstPage,
764+
'navigateToPage': () => undefined,
765+
'next': () => new FieldsPurger(fieldsUtils).clearHiddenFields(
766+
caseEditComponent.form, caseEditComponent.wizard, caseEditComponent.eventTrigger, firstPage.id),
767+
'cancel': () => undefined,
768+
'cancelled': cancelled,
769+
'submit': createSpy('submit').and.returnValue({
770+
// Provide a dummy subscribe function to be called in place of the real one
771+
subscribe: () => {}
772+
})
773+
};
774+
formErrorService = createSpyObj<FormErrorService>('formErrorService', ['mapFieldErrors']);
775+
formValueService = createSpyObj<FormValueService>('formValueService', ['sanitise']);
776+
formValueService.sanitise.and.callFake((rawValue: object) => {
777+
return rawValue;
778+
});
779+
780+
profileService = createSpyObj<ProfileService>('profileService', ['get']);
781+
profileService.get.and.returnValue(PROFILE_OBS);
782+
profileNotifier = new ProfileNotifier();
783+
profileNotifier.profile = new BehaviorSubject(createAProfile()).asObservable();
784+
profileNotifierSpy = spyOn(profileNotifier, 'announceProfile').and.callThrough();
785+
786+
TestBed.configureTestingModule({
787+
declarations: [
788+
CaseEditSubmitComponent,
789+
IsCompoundPipe,
790+
CaseReferencePipe
791+
],
792+
schemas: [NO_ERRORS_SCHEMA],
793+
providers: [
794+
{ provide: CaseEditComponent, useValue: caseEditComponent },
795+
{ provide: FormValueService, useValue: formValueService },
796+
{ provide: FormErrorService, useValue: formErrorService },
797+
{ provide: CaseFieldService, useValue: caseFieldService },
798+
{ provide: FieldsUtils, useValue: fieldsUtils },
799+
{ provide: CaseReferencePipe, useValue: casesReferencePipe },
800+
{ provide: ActivatedRoute, useValue: mockRouteNoProfile },
801+
{ provide: OrderService, useValue: orderService },
802+
{ provide: ProfileService, useValue: profileService },
803+
{ provide: ProfileNotifier, useValue: profileNotifier }
804+
]
805+
}).compileComponents();
806+
}));
807+
808+
beforeEach(() => {
809+
fixture = TestBed.createComponent(CaseEditSubmitComponent);
810+
comp = fixture.componentInstance;
811+
de = fixture.debugElement;
812+
fixture.detectChanges();
813+
});
814+
815+
it('should submit CaseEventData with null for any hidden fields whose values are not to be retained', () => {
816+
// Trigger the clearing of hidden fields by invoking next()
817+
caseEditComponent.next();
818+
819+
// Submit the form and check the expected CaseEventData is being passed to the CaseEditComponent for submission
820+
comp.submit();
821+
expect(caseEditComponent.submit).toHaveBeenCalledWith({
822+
data: {
823+
field2: null,
824+
field3: 'Hide both'
825+
},
826+
event_token: undefined,
827+
ignore_warning: false
828+
});
829+
});
830+
});
695831
});

src/shared/components/case-editor/case-edit-submit/case-edit-submit.component.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ export class CaseEditSubmitComponent implements OnInit, OnDestroy {
8787

8888
submit(): void {
8989
this.isSubmitting = true;
90-
let caseEventData: CaseEventData = this.formValueService.sanitise(this.editForm.value) as CaseEventData;
90+
let caseEventData: CaseEventData = this.formValueService.sanitise(this.filterRawFormValues(this.editForm).value) as CaseEventData;
9191
caseEventData.event_token = this.eventTrigger.event_token;
9292
caseEventData.ignore_warning = this.ignoreWarning;
9393
this.caseEdit.submit(caseEventData)
@@ -112,6 +112,33 @@ export class CaseEditSubmitComponent implements OnInit, OnDestroy {
112112
);
113113
}
114114

115+
/**
116+
* Filter *all* the values of a {@link FormGroup}, including those for disabled fields (i.e. hidden ones), removing
117+
* any whose {@link FormControl} is hidden (disabled) AND whose value is to be retained (i.e. `retain_hidden_value`
118+
* = `true`). This is necessary to allow hidden fields whose values are **not** to be retained, to be passed to the
119+
* backend to be updated.
120+
*
121+
* @param formGroup The `FormGroup` instance whose raw values are to be filtered
122+
* @returns `formGroup` with the filtered raw form value data (as key-value pairs) in place of the original value data
123+
*/
124+
private filterRawFormValues(formGroup: FormGroup): any {
125+
// Get the raw form value data, which includes the values of any disabled controls, as key-value pairs
126+
const rawFormValueData = formGroup.getRawValue().data;
127+
128+
// Discard any value whose FormControl is hidden (status = DISABLED) AND corresponds to a case_field with the
129+
// retain_hidden_value flag set to true (these fields should not be updated in the backend)
130+
Object.keys(rawFormValueData).forEach((key) => {
131+
const case_field = this.eventTrigger.case_fields[key];
132+
if ((formGroup.get('data') as FormGroup).get(key).status === 'DISABLED' && case_field && case_field.retain_hidden_value) {
133+
delete rawFormValueData[key];
134+
}
135+
});
136+
137+
// Set the filtered raw form value data back on the FormGroup
138+
formGroup.value.data = rawFormValueData;
139+
return formGroup;
140+
}
141+
115142
isDisabled(): boolean {
116143
return this.isSubmitting || !this.editForm.valid || this.hasErrors();
117144
}

src/shared/components/case-editor/case-edit/case-edit.component.spec.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ describe('CaseEditComponent', () => {
7777

7878
const CASE_FIELD_2: CaseField = <CaseField>({
7979
id: 'PersonLastName',
80-
label: 'First name',
80+
label: 'Last name',
8181
field_type: null,
8282
display_context: 'READONLY'
8383
});
@@ -538,14 +538,18 @@ describe('CaseEditComponent', () => {
538538
expect(component.form.get('data').get(CASE_FIELD_3.id)).not.toBeNull();
539539
});
540540

541-
it('should navigate to next page when next is called and clear hidden simple form field', () => {
541+
it('should navigate to next page when next is called and clear hidden simple form fields with retain_hidden_value true', () => {
542542
component.wizard = wizard;
543543
let currentPage = new WizardPage();
544544
currentPage.wizard_page_fields = [WIZARD_PAGE_1];
545545
currentPage.case_fields = [CASE_FIELD_1];
546546
wizard.getPage.and.returnValue(currentPage);
547547
let nextPage = new WizardPage();
548548
nextPage.show_condition = 'PersonFirstName=\"John\"';
549+
// Ensure retain_hidden_value is true for fields that will be cleared but whose value is to be retained in
550+
// the backend (i.e. form control removed to prevent backend update)
551+
CASE_FIELD_2.retain_hidden_value = true;
552+
CASE_FIELD_3.retain_hidden_value = true;
549553
nextPage.case_fields = [CASE_FIELD_2, CASE_FIELD_3]
550554
nextPage.wizard_page_fields = [WIZARD_PAGE_2, WIZARD_PAGE_3];
551555
wizard.nextPage.and.returnValue(nextPage);

src/shared/domain/definition/case-field.model.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export class CaseField implements Orderable {
2424
show_summary_content_option?: number;
2525
acls?: AccessControlList[];
2626
metadata?: boolean;
27+
retain_hidden_value: boolean;
2728

2829
@Type(() => WizardPageField)
2930
wizardProps?: WizardPageField;

src/shared/fixture/shared.test.fixture.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ export let createCaseEventTrigger = (id: string,
2929
};
3030

3131
export let aCaseField = (id: string, label: string, type: FieldTypeEnum, display_context: string,
32-
show_summary_content_option: number, typeComplexFields: CaseField[] = []): CaseField => {
32+
show_summary_content_option: number, typeComplexFields: CaseField[] = [],
33+
retain_hidden_value?: boolean): CaseField => {
3334
return <CaseField>({
3435
id: id || 'personFirstName',
3536
field_type: {
@@ -39,7 +40,8 @@ export let aCaseField = (id: string, label: string, type: FieldTypeEnum, display
3940
},
4041
display_context: display_context || 'OPTIONAL',
4142
label: label || 'First name',
42-
show_summary_content_option: show_summary_content_option
43+
show_summary_content_option: show_summary_content_option,
44+
retain_hidden_value: retain_hidden_value || false
4345
});
4446
};
4547

0 commit comments

Comments
 (0)