-
-
Notifications
You must be signed in to change notification settings - Fork 409
chore(#10700): re-write Enketo form save workflow #11256
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 31 commits
6df4caf
426cc44
df13baf
2cefd19
04dc956
8aac455
e2c6fad
c8572c8
ce42f0f
56e5eb0
ac02e3d
eeeb217
b7c6cb8
b009775
171a25d
e533acd
ed3024d
bf5286e
ec882d7
6f0c8cd
d354e56
e4b7e32
9e5fa1c
f10f949
046178a
8975432
2fd0db6
e0e4e72
cd23e70
d09a4db
6844aaf
f9ab731
e5cdabc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,11 +18,11 @@ import { MatAccordion } from '@angular/material/expansion'; | |
| import { EnketoComponent } from '@mm-components/enketo/enketo.component'; | ||
| import { TranslatePipe } from '@ngx-translate/core'; | ||
| import { DuplicateContactsComponent } from '@mm-components/duplicate-contacts/duplicate-contacts.component'; | ||
| import { DuplicateCheck } from '@mm-services/deduplicate.service'; | ||
| import { Contact, Qualifier } from '@medic/cht-datasource'; | ||
| import { TelemetryService } from '@mm-services/telemetry.service'; | ||
| import { CHTDatasourceService } from '@mm-services/cht-datasource.service'; | ||
| import events from 'enketo-core/src/js/event'; | ||
| import { XmlFormsService } from '@mm-services/xml-forms.service'; | ||
| import { FormValidationError } from '@mm-services/enketo.service'; | ||
|
|
||
| @Component({ | ||
| templateUrl: './contacts-edit.component.html', | ||
|
|
@@ -37,6 +37,7 @@ export class ContactsEditComponent implements OnInit, OnDestroy, AfterViewInit { | |
| private readonly formService: FormService, | ||
| private readonly contactTypesService: ContactTypesService, | ||
| private readonly dbService: DbService, | ||
| private readonly xmlFormsService: XmlFormsService, | ||
| private readonly performanceService: PerformanceService, | ||
| private readonly telemetryService: TelemetryService, | ||
| readonly chtDatasourceService: CHTDatasourceService, | ||
|
|
@@ -50,8 +51,7 @@ export class ContactsEditComponent implements OnInit, OnDestroy, AfterViewInit { | |
|
|
||
| subscription = new Subscription(); | ||
| translationsLoadedSubscription; | ||
| private globalActions; | ||
| private xmlVersion; | ||
| private readonly globalActions; | ||
| private readonly getContactFromDatasource: ReturnType<typeof Contact.v1.get>; | ||
|
|
||
| enketoStatus; | ||
|
|
@@ -70,9 +70,8 @@ export class ContactsEditComponent implements OnInit, OnDestroy, AfterViewInit { | |
| private trackRender; | ||
| private trackEditDuration; | ||
| private trackSave; | ||
| private trackMetadata = { action: '', form: '' }; | ||
| private readonly trackMetadata = { action: '', form: '' }; | ||
|
|
||
| private duplicateCheck?: DuplicateCheck; | ||
| duplicatesAcknowledged = false; | ||
|
|
||
| duplicates: Contact.v1.Contact[] = []; | ||
|
|
@@ -323,13 +322,10 @@ export class ContactsEditComponent implements OnInit, OnDestroy, AfterViewInit { | |
| } | ||
|
|
||
| private async renderForm(formId: string, titleKey: string) { | ||
| const formDoc = await this.dbService.get().get(formId); | ||
| this.xmlVersion = formDoc.xmlVersion; | ||
| this.duplicateCheck = formDoc.duplicate_check; | ||
|
|
||
| const formConfig = await this.xmlFormsService.getFormConfig('contact', formId); | ||
| this.globalActions.setEnketoEditedStatus(false); | ||
|
|
||
| const formContext = new WebappEnketoFormContext('#contact-form', 'contact', formDoc, this.getFormInstanceData()); | ||
| const formContext = new WebappEnketoFormContext('#contact-form', formConfig, this.getFormInstanceData()); | ||
| formContext.editedListener = this.markFormEdited.bind(this); | ||
| formContext.valuechangeListener = this.resetFormError.bind(this); | ||
| formContext.titleKey = titleKey; | ||
|
|
@@ -434,64 +430,55 @@ export class ContactsEditComponent implements OnInit, OnDestroy, AfterViewInit { | |
| this.globalActions.setEnketoSavingStatus(true); | ||
| this.globalActions.setEnketoError(null); | ||
|
|
||
| return Promise | ||
| .resolve(form.validate()) | ||
| .then((valid) => { | ||
| if (!valid) { | ||
| throw new Error('Validation failed.'); | ||
| } | ||
|
Comment on lines
-438
to
-442
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is now done in the EnketoService.saveContact. |
||
| if (this.duplicatesAcknowledged && this.duplicates.length) { | ||
| this.telemetryService.record( | ||
| ['enketo', 'contacts', this.enketoContact.type, 'duplicates_acknowledged'].join(':') | ||
| ); | ||
| } | ||
|
Comment on lines
+433
to
+437
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The most important thing is that this runs before the dupchecking logic in |
||
|
|
||
| // Updating fields before save. Ref: #6670. | ||
| form.view.html.dispatchEvent(events.BeforeSave()); | ||
| return this.formService | ||
| .saveContact( | ||
| { docId, type: this.enketoContact.type }, | ||
| form, | ||
| this.duplicatesAcknowledged | ||
| ) | ||
| .then((result) => { | ||
| console.debug('saved contact', result); | ||
|
|
||
| if (this.duplicatesAcknowledged && this.duplicates.length) { | ||
| this.telemetryService.record( | ||
| ['enketo', 'contacts', this.enketoContact.type, 'duplicates_acknowledged'].join(':') | ||
| ); | ||
| } | ||
| this.globalActions.setEnketoSavingStatus(false); | ||
| this.globalActions.setEnketoEditedStatus(false); | ||
|
|
||
| this.trackSave?.stop({ | ||
| name: ['enketo', 'contacts', this.trackMetadata.form, this.trackMetadata.action, 'save'].join(':'), | ||
| recordApdex: true, | ||
| }); | ||
|
|
||
| this.translateService | ||
| .get(docId ? 'contact.updated' : 'contact.created') | ||
| .then(snackBarContent => this.globalActions.setSnackbarContent(snackBarContent)); | ||
|
|
||
| return this.formService | ||
| .saveContact( | ||
| { docId, type: this.enketoContact.type }, | ||
| { form, xmlVersion: this.xmlVersion, duplicateCheck: this.duplicateCheck}, | ||
| this.duplicatesAcknowledged | ||
| ) | ||
| .then((result) => { | ||
| console.debug('saved contact', result); | ||
|
|
||
| this.globalActions.setEnketoSavingStatus(false); | ||
| this.globalActions.setEnketoEditedStatus(false); | ||
|
|
||
| this.trackSave?.stop({ | ||
| name: ['enketo', 'contacts', this.trackMetadata.form, this.trackMetadata.action, 'save'].join(':'), | ||
| recordApdex: true, | ||
| }); | ||
|
|
||
| this.translateService | ||
| .get(docId ? 'contact.updated' : 'contact.created') | ||
| .then(snackBarContent => this.globalActions.setSnackbarContent(snackBarContent)); | ||
|
|
||
| this.router.navigate(['/contacts', result.docId]); | ||
| }) | ||
| .catch((err) => { | ||
| if (err instanceof DuplicatesFoundError) { | ||
| this.duplicates = err.duplicates; | ||
| } else { | ||
| this.duplicates = []; | ||
| } | ||
|
|
||
| console.error('Error submitting form data', err); | ||
|
|
||
| this.globalActions.setEnketoSavingStatus(false); | ||
| return this.translateService | ||
| .get('Error updating contact') | ||
| .then(error => this.globalActions.setEnketoError(error)); | ||
| }); | ||
| this.router.navigate(['/contacts', result.docId]); | ||
| }) | ||
| .catch(() => { | ||
| // validation messages will be displayed for individual fields. | ||
| // That's all we want, really. | ||
| .catch((err) => { | ||
| this.globalActions.setEnketoSavingStatus(false); | ||
|
|
||
| if (err instanceof FormValidationError) { | ||
| // validation messages will be displayed for individual fields. | ||
| // That's all we want, really. | ||
| return; | ||
| } | ||
|
|
||
| if (err instanceof DuplicatesFoundError) { | ||
| this.duplicates = err.duplicates; | ||
| } else { | ||
| this.duplicates = []; | ||
| } | ||
|
|
||
| console.error('Error submitting form data', err); | ||
|
|
||
| return this.translateService | ||
| .get('Error updating contact') | ||
| .then(error => this.globalActions.setEnketoError(error)); | ||
| }); | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The changes here are actually due to the updates I made in the cht-form/src/app.component.ts. When we write a doc to Couch, properties set to
undefinedare not included in the JSON data. cht-form returns the JS objects directly and was not doing anything to account forundefinedproperties. Unfortunately, Chai treats an object with a property set toundefinedas not deep equal to an object that just does not have the property at all. So, these values were previously needed here to account for these props getting set in the Enekto service, etc.