From 9803bd945777e84b57ff818ef6a96cb0c4b2b266 Mon Sep 17 00:00:00 2001 From: Leonardo Mendoza Fernadez Date: Wed, 25 Sep 2024 16:05:37 -0600 Subject: [PATCH] lmendoza/sep-24 --- .../observability-events.service.ts | 6 +- .../form-current-employment.component.ts | 42 +++++++++---- .../form-password/form-password.component.ts | 24 ++++++-- .../form-personal/form-personal.component.ts | 27 +++++++-- .../form-visibility.component.ts | 25 ++++++-- .../components/step-a/step-a.component.ts | 1 - .../components/step-b/step-b.component.ts | 1 - .../components/step-c/step-c.component.ts | 1 - .../components/step-c2/step-c2.component.ts | 1 - .../components/step-d/step-d.component.ts | 1 - .../pages/register/register2.component.ts | 59 +++++++++++++------ .../register-observability.service.ts | 41 +++++++++++-- 12 files changed, 168 insertions(+), 61 deletions(-) diff --git a/src/app/core/observability-events/observability-events.service.ts b/src/app/core/observability-events/observability-events.service.ts index db81a9b47..4ab28c57c 100644 --- a/src/app/core/observability-events/observability-events.service.ts +++ b/src/app/core/observability-events/observability-events.service.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@angular/core' import { WINDOW } from 'src/app/cdk/window' import { environment } from 'src/environments/environment' -export type journeyType = 'orcid_registration' | 'orcid_update_emails' +export type JourneyType = 'orcid_registration' | 'orcid_update_emails' @Injectable({ providedIn: 'root', }) @@ -18,7 +18,7 @@ export class CustomEventService { * @param journeyType The type of the journey (e.g., 'orcid_registration', 'orcid_update_emails'). * @param attributes Additional attributes to store with the journey */ - startJourney(journeyType: journeyType, attributes: any = {}): void { + startJourney(journeyType: JourneyType, attributes: any = {}): void { // Record the start time and initial attributes @@ -41,7 +41,7 @@ export class CustomEventService { * @param additionalAttributes Any additional attributes related to the event. */ recordEvent( - journeyType: journeyType, + journeyType: JourneyType, eventName: string, additionalAttributes: any = {} ): void { diff --git a/src/app/register2/components/form-current-employment/form-current-employment.component.ts b/src/app/register2/components/form-current-employment/form-current-employment.component.ts index c92568e11..67576ba86 100644 --- a/src/app/register2/components/form-current-employment/form-current-employment.component.ts +++ b/src/app/register2/components/form-current-employment/form-current-employment.component.ts @@ -1,4 +1,11 @@ -import { Component, forwardRef, Input, OnInit, ViewChild } from '@angular/core' +import { + Component, + forwardRef, + Input, + OnDestroy, + OnInit, + ViewChild, +} from '@angular/core' import { FormBuilder, FormControl, @@ -14,7 +21,7 @@ import { import { Register2Service } from 'src/app/core/register2/register2.service' import { OrcidValidators } from 'src/app/validators' -import { first, switchMap, tap } from 'rxjs/operators' +import { first, switchMap, takeUntil, tap } from 'rxjs/operators' import { ReactivationService } from '../../../core/reactivation/reactivation.service' import { ReactivationLocal } from '../../../types/reactivation.local' import { BaseForm } from '../BaseForm' @@ -27,7 +34,7 @@ import { AffiliationType, Organization, } from 'src/app/types/record-affiliation.endpoint' -import { EMPTY, Observable, of } from 'rxjs' +import { EMPTY, Observable, of, Subject } from 'rxjs' import { RecordAffiliationService } from 'src/app/core/record-affiliations/record-affiliations.service' import { dateMonthYearValidator } from 'src/app/shared/validators/date/date.validator' import { RegisterStateService } from '../../register-state.service' @@ -70,7 +77,10 @@ export class MyErrorStateMatcher implements ErrorStateMatcher { }, ], }) -export class FormCurrentEmploymentComponent extends BaseForm implements OnInit { +export class FormCurrentEmploymentComponent + extends BaseForm + implements OnInit, OnDestroy +{ // matcher = new MyErrorStateMatcher() selectedOrganizationFromDatabase: Organization displayOrganizationHint: boolean @@ -78,6 +88,7 @@ export class FormCurrentEmploymentComponent extends BaseForm implements OnInit { private _type: AffiliationType affiliationFound = false rorIdHasBeenMatched: boolean + destroy = new Subject() @Input() public get type(): AffiliationType { @@ -129,15 +140,24 @@ export class FormCurrentEmploymentComponent extends BaseForm implements OnInit { this.isMobile = platform.columns4 || platform.columns8 }) } + ngOnDestroy(): void { + this.destroy.next() + } ngOnInit() { - this.registerStateService.getNextButtonClickFor('c2').subscribe(() => { - this.nextButtonWasClicked = true - this._registerObservabilityService.stepC2NextButtonClicked(this.form) - }) - this.registerStateService.getSkipButtonClickFor('c2').subscribe(() => { - this._registerObservabilityService.stepC2SkipButtonClicked(this.form) - }) + this.registerStateService + .getNextButtonClickFor('c2') + .pipe(takeUntil(this.destroy)) + .subscribe(() => { + this.nextButtonWasClicked = true + this._registerObservabilityService.stepC2NextButtonClicked(this.form) + }) + this.registerStateService + .getSkipButtonClickFor('c2') + .pipe(takeUntil(this.destroy)) + .subscribe(() => { + this._registerObservabilityService.stepC2SkipButtonClicked(this.form) + }) this.registerStateService.matchOrganization$.subscribe((organization) => { this.organization = organization this.form.patchValue({ diff --git a/src/app/register2/components/form-password/form-password.component.ts b/src/app/register2/components/form-password/form-password.component.ts index a5675a3a0..8941f85c0 100644 --- a/src/app/register2/components/form-password/form-password.component.ts +++ b/src/app/register2/components/form-password/form-password.component.ts @@ -3,6 +3,7 @@ import { Component, forwardRef, Input, + OnDestroy, OnInit, ViewChild, } from '@angular/core' @@ -24,6 +25,8 @@ import { LiveAnnouncer } from '@angular/cdk/a11y' import { environment } from 'src/environments/environment' import { RegisterObservabilityService } from '../../register-observability.service' import { RegisterStateService } from '../../register-state.service' +import { Subject } from 'rxjs' +import { takeUntil } from 'rxjs/operators' @Component({ selector: 'app-form-password', @@ -48,7 +51,7 @@ import { RegisterStateService } from '../../register-state.service' ], preserveWhitespaces: true, }) -export class FormPasswordComponent extends BaseForm implements OnInit { +export class FormPasswordComponent extends BaseForm implements OnInit, OnDestroy { labelInfo = $localize`:@@register.ariaLabelInfoPassword:info about password` labelClose = $localize`:@@register.ariaLabelClose:close` labelConfirmPassword = $localize`:@@register.confirmYourPassword:Confirm your password` @@ -67,12 +70,12 @@ export class FormPasswordComponent extends BaseForm implements OnInit { @Input() personalData: RegisterForm nextButtonWasClicked: boolean - currentValidate8orMoreCharactersStatus: boolean ccurentValidateAtLeastALetterOrSymbolStatus: boolean currentValidateAtLeastANumber: boolean passwordsValidAreValidAlreadyChecked: any _currentAccesibilityError: string + destroy = new Subject() constructor( private _register: Register2Service, private _liveAnnouncer: LiveAnnouncer, @@ -83,10 +86,13 @@ export class FormPasswordComponent extends BaseForm implements OnInit { super() } ngOnInit() { - this._registerStateService.getNextButtonClickFor('b').subscribe((value) => { - this.nextButtonWasClicked = true - this._registerObservability.stepBNextButtonClicked(this.form) - }) + this._registerStateService + .getNextButtonClickFor('b') + .pipe(takeUntil(this.destroy)) + .subscribe((value) => { + this.nextButtonWasClicked = true + this._registerObservability.stepBNextButtonClicked(this.form) + }) this.form = new UntypedFormGroup( { password: new UntypedFormControl('', { @@ -222,6 +228,7 @@ export class FormPasswordComponent extends BaseForm implements OnInit { } this._currentAccesibilityError = value if (!value) { + this._registerObservability.reportRegisterEvent('password_valid') this.announce( $localize`:@@register.allPasswordContrainsArMet:All password constraints are met` ) @@ -254,6 +261,7 @@ export class FormPasswordComponent extends BaseForm implements OnInit { const validStatus = this.confirmPasswordValid && this.passwordValid if (!this.passwordsValidAreValidAlreadyChecked && validStatus) { + this._registerObservability.reportRegisterEvent('password_match') this.announce( $localize`:@@register.passwordAreValid:Your passwords match` ) @@ -305,4 +313,8 @@ export class FormPasswordComponent extends BaseForm implements OnInit { } this._liveAnnouncer.announce(announcement, 'assertive') } + + ngOnDestroy(): void { + this.destroy.next() + } } diff --git a/src/app/register2/components/form-personal/form-personal.component.ts b/src/app/register2/components/form-personal/form-personal.component.ts index bf5728fa7..5eb0a892f 100644 --- a/src/app/register2/components/form-personal/form-personal.component.ts +++ b/src/app/register2/components/form-personal/form-personal.component.ts @@ -5,6 +5,7 @@ import { forwardRef, Inject, Input, + OnDestroy, OnInit, ViewChild, } from '@angular/core' @@ -30,6 +31,7 @@ import { startWith, switchMap, take, + takeUntil, } from 'rxjs/operators' import { ReactivationService } from '../../../core/reactivation/reactivation.service' import { ReactivationLocal } from '../../../types/reactivation.local' @@ -51,7 +53,8 @@ import { ErrorHandlerService } from 'src/app/core/error-handler/error-handler.se import { ERROR_REPORT } from 'src/app/errors' import { RegisterStateService } from '../../register-state.service' import { RegisterObservabilityService } from '../../register-observability.service' -export class MyErrorStateMatcher implements ErrorStateMatcher { +import { Subject } from 'rxjs' +export class MyErrorStateMatcher implements ErrorStateMatcher{ isErrorState( control: FormControl | null, form: FormGroupDirective | NgForm | null @@ -87,7 +90,10 @@ export class MyErrorStateMatcher implements ErrorStateMatcher { }, ], }) -export class FormPersonalComponent extends BaseForm implements OnInit { +export class FormPersonalComponent + extends BaseForm + implements OnInit, OnDestroy +{ matcher = new MyErrorStateMatcher() @Input() reactivation: ReactivationLocal @ViewChild(FormGroupDirective) formGroupDir: FormGroupDirective @@ -105,6 +111,7 @@ export class FormPersonalComponent extends BaseForm implements OnInit { emailsAreValidAlreadyChecked: boolean registerBackendErrors: RegisterBackendErrors nextButtonWasClicked: boolean + destroy = new Subject() constructor( private _register: Register2Service, @@ -121,6 +128,9 @@ export class FormPersonalComponent extends BaseForm implements OnInit { ) { super() } + ngOnDestroy(): void { + this.destroy.next() + } emails: UntypedFormGroup = new UntypedFormGroup({}) additionalEmails: UntypedFormGroup = new UntypedFormGroup({ @@ -131,10 +141,13 @@ export class FormPersonalComponent extends BaseForm implements OnInit { }) ngOnInit() { - this._registerStateService.getNextButtonClickFor('a').subscribe((value) => { - this.nextButtonWasClicked = true - this._registerObservability.stepANextButtonClicked(this.form) - }) + this._registerStateService + .getNextButtonClickFor('a') + .pipe(takeUntil(this.destroy)) + .subscribe((value) => { + this.nextButtonWasClicked = true + this._registerObservability.stepANextButtonClicked(this.form) + }) this.emails = new UntypedFormGroup( { email: new UntypedFormControl('', { @@ -318,7 +331,9 @@ export class FormPersonalComponent extends BaseForm implements OnInit { const validStatus = this.emailConfirmationValid && this.emailValid if (!this.emailsAreValidAlreadyChecked && validStatus) { this.announce($localize`:@@register.emailAreValid:Your emails match`) + this._registerObservability.reportRegisterEvent('emails_match') } else if (this.emailsAreValidAlreadyChecked && !validStatus) { + this._registerObservability.reportRegisterEvent('emails_do_not_match') this.announce( $localize`:@@register.emailAreNotValid:Your emails do not match` ) diff --git a/src/app/register2/components/form-visibility/form-visibility.component.ts b/src/app/register2/components/form-visibility/form-visibility.component.ts index 431e900e3..c1cb29ed2 100644 --- a/src/app/register2/components/form-visibility/form-visibility.component.ts +++ b/src/app/register2/components/form-visibility/form-visibility.component.ts @@ -1,4 +1,10 @@ -import { Component, DoCheck, forwardRef, OnInit } from '@angular/core' +import { + Component, + DoCheck, + forwardRef, + OnDestroy, + OnInit, +} from '@angular/core' import { NG_ASYNC_VALIDATORS, NG_VALUE_ACCESSOR, @@ -13,6 +19,8 @@ import { Register2Service } from 'src/app/core/register2/register2.service' import { BaseForm } from '../BaseForm' import { RegisterStateService } from '../../register-state.service' import { RegisterObservabilityService } from '../../register-observability.service' +import { Subject } from 'rxjs' +import { takeUntil } from 'rxjs/operators' @Component({ selector: 'app-form-visibility', @@ -38,12 +46,13 @@ import { RegisterObservabilityService } from '../../register-observability.servi }) export class FormVisibilityComponent extends BaseForm - implements OnInit, DoCheck + implements OnInit, DoCheck, OnDestroy { ariaLabelMoreInformationOnVisibility = $localize`:@@register.ariaLabelMoreInformationOnVisibility:More information on visibility settings (Opens in new tab)` visibilityOptions = VISIBILITY_OPTIONS errorState = false activitiesVisibilityDefault = new UntypedFormControl('', Validators.required) + destroy = new Subject() constructor( private _register: Register2Service, private _errorStateMatcher: ErrorStateMatcher, @@ -52,10 +61,16 @@ export class FormVisibilityComponent ) { super() } + ngOnDestroy(): void { + this.destroy.next() + } ngOnInit() { - this._registerStateService.getNextButtonClickFor('c').subscribe(() => { - this._registerObservability.stepCNextButtonClicked(this.form) - }) + this._registerStateService + .getNextButtonClickFor('c') + .pipe(takeUntil(this.destroy)) + .subscribe(() => { + this._registerObservability.stepCNextButtonClicked(this.form) + }) this.form = new UntypedFormGroup({ activitiesVisibilityDefault: this.activitiesVisibilityDefault, }) diff --git a/src/app/register2/components/step-a/step-a.component.ts b/src/app/register2/components/step-a/step-a.component.ts index f5d4b423c..9e0d3cdbb 100644 --- a/src/app/register2/components/step-a/step-a.component.ts +++ b/src/app/register2/components/step-a/step-a.component.ts @@ -46,7 +46,6 @@ export class StepAComponent } ngOnInit(): void { - this._registerObservabilityService.stepLoaded('a') } infoSiteBaseUrl = environment.INFO_SITE diff --git a/src/app/register2/components/step-b/step-b.component.ts b/src/app/register2/components/step-b/step-b.component.ts index a72f45066..368f0f410 100644 --- a/src/app/register2/components/step-b/step-b.component.ts +++ b/src/app/register2/components/step-b/step-b.component.ts @@ -25,7 +25,6 @@ export class StepBComponent extends BaseStepDirective implements OnInit { super() } ngOnInit(): void { - this._registerObservabilityService.stepLoaded('b') } nextButtonWasClicked = false diff --git a/src/app/register2/components/step-c/step-c.component.ts b/src/app/register2/components/step-c/step-c.component.ts index 234d33c18..8a903a3d0 100644 --- a/src/app/register2/components/step-c/step-c.component.ts +++ b/src/app/register2/components/step-c/step-c.component.ts @@ -25,7 +25,6 @@ export class StepCComponent extends BaseStepDirective implements OnInit { super() } ngOnInit(): void { - this._registerObservabilityService.stepLoaded('c') } nextButton2() { diff --git a/src/app/register2/components/step-c2/step-c2.component.ts b/src/app/register2/components/step-c2/step-c2.component.ts index dd1cc89c7..b96662566 100644 --- a/src/app/register2/components/step-c2/step-c2.component.ts +++ b/src/app/register2/components/step-c2/step-c2.component.ts @@ -28,7 +28,6 @@ export class StepC2Component extends BaseStepDirective implements OnInit { super() } ngOnInit(): void { - this._registerObservabilityService.stepLoaded('c2') } optionalNextStep() { diff --git a/src/app/register2/components/step-d/step-d.component.ts b/src/app/register2/components/step-d/step-d.component.ts index 5894c7b18..635d02f3a 100644 --- a/src/app/register2/components/step-d/step-d.component.ts +++ b/src/app/register2/components/step-d/step-d.component.ts @@ -27,7 +27,6 @@ export class StepDComponent extends BaseStepDirective { super() } ngOnInit(): void { - this._registerObservabilityService.stepLoaded('d') } nextButton2() { diff --git a/src/app/register2/pages/register/register2.component.ts b/src/app/register2/pages/register/register2.component.ts index 035824b26..795669cac 100644 --- a/src/app/register2/pages/register/register2.component.ts +++ b/src/app/register2/pages/register/register2.component.ts @@ -32,7 +32,11 @@ import { ThirdPartyAuthData } from 'src/app/types/sign-in-data.endpoint' import { GoogleTagManagerService } from '../../../core/google-tag-manager/google-tag-manager.service' import { SearchService } from '../../../core/search/search.service' import { ReactivationLocal } from '../../../types/reactivation.local' -import { CustomEventService } from 'src/app/core/observability-events/observability-events.service' +import { + CustomEventService, + JourneyType, +} from 'src/app/core/observability-events/observability-events.service' +import { RegisterObservabilityService } from '../../register-observability.service' @Component({ selector: 'app-register-2', @@ -49,6 +53,8 @@ export class Register2Component implements OnInit, AfterViewInit { @ViewChild('stepComponentA', { read: ElementRef }) stepComponentA: ElementRef @ViewChild('stepComponentB', { read: ElementRef }) stepComponentB: ElementRef @ViewChild('stepComponentC', { read: ElementRef }) stepComponentC: ElementRef + @ViewChild('stepComponentC2', { read: ElementRef }) + stepComponentC2: ElementRef @ViewChild('stepComponentD', { read: ElementRef }) stepComponentD: ElementRef platform: PlatformInfo @@ -77,26 +83,27 @@ export class Register2Component implements OnInit, AfterViewInit { private _platformInfo: PlatformInfoService, private _formBuilder: UntypedFormBuilder, private _register: Register2Service, - private _dialog: MatDialog, @Inject(WINDOW) private window: Window, private _googleTagManagerService: GoogleTagManagerService, - private _user: UserService, private _router: Router, private _errorHandler: ErrorHandlerService, private _userInfo: UserService, - private _searchService: SearchService, - private _observability: CustomEventService, + private _registerObservabilityService: RegisterObservabilityService ) { _platformInfo.get().subscribe((platform) => { this.platform = platform this.reactivation.isReactivation = this.platform.reactivation this.reactivation.reactivationCode = this.platform.reactivationCode + + this._registerObservabilityService.initializeJourney({ + isReactivation: this.reactivation.isReactivation, + coulumn4: this.platform.columns4, + column8: this.platform.columns8, + column12: this.platform.columns12, + }) }) } ngOnInit() { - - this._observability.startJourney('orcid_registration') - this._register.getRegisterForm().subscribe() this.FormGroupStepA = this._formBuilder.group({ @@ -131,6 +138,9 @@ export class Register2Component implements OnInit, AfterViewInit { this.requestInfoForm = session.oauthSession if (this.thirdPartyAuthData || this.requestInfoForm) { + this._registerObservabilityService.reportRegisterEvent( + 'prefill_register-form' + ) this.FormGroupStepA = this.prefillRegisterForm( this.requestInfoForm, this.thirdPartyAuthData @@ -170,17 +180,22 @@ export class Register2Component implements OnInit, AfterViewInit { ) .pipe( switchMap((validator: RegisterForm) => { + this._registerObservabilityService.reportRegisterEvent( + 'register-validate', + { + validator, + } + ) if (validator.errors.length > 0) { // At this point any backend error is unexpected this._errorHandler.handleError( new Error('registerUnexpectedValidateFail'), ERROR_REPORT.REGISTER ) - this._observability.recordEvent( - 'orcid_registration', - 'register-unexpected-validate-fail', + this._registerObservabilityService.reportRegisterErrorEvent( + 'register-validate-fail', { - validator, + errors: validator.errors, } ) } @@ -198,13 +213,13 @@ export class Register2Component implements OnInit, AfterViewInit { .subscribe((response) => { this.loading = false if (response.url) { - this._observability.recordEvent( - 'orcid_registration', - 'register-success', + this._registerObservabilityService.reportRegisterEvent( + 'register-confirmation', { response, } ) + const analyticsReports: Observable[] = [] analyticsReports.push( @@ -227,13 +242,13 @@ export class Register2Component implements OnInit, AfterViewInit { () => this.afterRegisterRedirectionHandler(response) ) } else { - this._observability.recordEvent( - 'orcid_registration', - 'register-unexpected-confirmation', + this._registerObservabilityService.reportRegisterErrorEvent( + 'register-confirmation', { response, } ) + this._errorHandler.handleError( new Error('registerUnexpectedConfirmation'), ERROR_REPORT.REGISTER @@ -267,6 +282,8 @@ export class Register2Component implements OnInit, AfterViewInit { } } selectionChange(event: StepperSelectionEvent) { + const step = ['a', 'b', 'c2', 'c', 'd'][event.selectedIndex] as JourneyType + this._registerObservabilityService.stepLoaded(step) if (this.platform.columns4 || this.platform.columns8) { this.focusCurrentStep(event) } @@ -281,8 +298,10 @@ export class Register2Component implements OnInit, AfterViewInit { } else if (event.selectedIndex === 1) { nextStep = this.stepComponentB } else if (event.selectedIndex === 2) { - nextStep = this.stepComponentC + nextStep = this.stepComponentC2 } else if (event.selectedIndex === 3) { + nextStep = this.stepComponentC + } else if (event.selectedIndex === 4) { nextStep = this.stepComponentD } // On mobile scroll the current step component into view @@ -292,6 +311,8 @@ export class Register2Component implements OnInit, AfterViewInit { nativeElementNextStep.scrollIntoView() }, 200) } + + // Report the step change } /** diff --git a/src/app/register2/register-observability.service.ts b/src/app/register2/register-observability.service.ts index 02ff9c2a9..118f9e62f 100644 --- a/src/app/register2/register-observability.service.ts +++ b/src/app/register2/register-observability.service.ts @@ -3,7 +3,10 @@ import { OrganizationsService } from '../core' import { Organization } from 'src/app/types/record-affiliation.endpoint' import { Subject } from 'rxjs' import { OrgDisambiguated } from '../types' -import { CustomEventService } from '../core/observability-events/observability-events.service' +import { + CustomEventService, + JourneyType, +} from '../core/observability-events/observability-events.service' import { UntypedFormArray, UntypedFormGroup } from '@angular/forms' import { RegisterStateService } from './register-state.service' import init from 'helphero' @@ -15,6 +18,7 @@ export class RegisterObservabilityService { matchOrganization$ = new Subject() primaryEmailMatched: Organization secondaryEmail: Organization + registrationJourneyStarted: any constructor( private _observability: CustomEventService, private _registrationStateService: RegisterStateService @@ -143,13 +147,38 @@ export class RegisterObservabilityService { ) } - stepLoaded(step: 'a' | 'b' | 'c' | 'c2' | 'd') { + stepLoaded(step: JourneyType) { this._observability.recordEvent('orcid_registration', `step-${step}-loaded`) } - initializeHelpHero(reactivation) { - this._observability.startJourney('orcid_registration', { - reactivation, - }) + initializeJourney(reactivation) { + if (!this.registrationJourneyStarted) { + this._observability.startJourney('orcid_registration', { + ...reactivation, + }) + this.registrationJourneyStarted = true + } + } + + completeJourney(attributes: any = {}) { + this._observability.recordEvent( + 'orcid_registration', + 'journey-complete', + attributes + ) + this._observability.finishJourney('orcid_registration') + this.registrationJourneyStarted = false + } + + reportRegisterEvent(eventName: string, attributes: any = {}) { + this._observability.recordEvent('orcid_registration', eventName, attributes) + } + + reportRegisterErrorEvent(eventName: string, attributes: any = {}) { + this._observability.recordEvent( + 'orcid_registration', + `${eventName}-error`, + attributes + ) } }