diff --git a/src/app/core/observability-events/observability-events.service.ts b/src/app/core/observability-events/observability-events.service.ts index c59ed619c..1c1105e1c 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 this.journeys[journeyType] = { startTime: Date.now(), @@ -27,7 +27,7 @@ export class CustomEventService { if (environment.debugger) { console.debug( - `-> Journey "${journeyType}" started at ${this.journeys[journeyType].startTime}` + `-> Journey "${journeyType}" started at ${this.journeys[journeyType].startTime}`, attributes ) } } @@ -39,7 +39,7 @@ export class CustomEventService { * @param additionalAttributes Any additional attributes related to the event. */ recordEvent( - journeyType: string, + journeyType: JourneyType, eventName: string, additionalAttributes: any = {} ): void { @@ -64,7 +64,8 @@ export class CustomEventService { if (environment.debugger) { console.debug( - `-> Event "${eventName}" recorded for journey "${journeyType}" with elapsed time ${elapsedTime}ms` + `-> Event "${eventName}" recorded for journey "${journeyType}" with elapsed time ${elapsedTime}ms`, + eventAttributes ) } } @@ -97,8 +98,8 @@ export class CustomEventService { // Clean up the journey data delete this.journeys[journeyType] - console.log( - `Journey "${journeyType}" finished with elapsed time ${elapsedTime}ms` + console.debug( + `Journey "${journeyType}" finished with elapsed time ${elapsedTime}ms`, finalAttributes ) } } 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 ef6c2dd63..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,11 +34,12 @@ 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' import { OrgDisambiguated } from 'src/app/types' +import { RegisterObservabilityService } from '../../register-observability.service' export class MyErrorStateMatcher implements ErrorStateMatcher { isErrorState( control: FormControl | null, @@ -69,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 @@ -77,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 { @@ -85,7 +97,7 @@ export class FormCurrentEmploymentComponent extends BaseForm implements OnInit { filteredOptions: Observable - @Input() nextButtonWasClicked: boolean + nextButtonWasClicked: boolean @Input() reactivation: ReactivationLocal @ViewChild(FormGroupDirective) formGroupDir: FormGroupDirective ariaLabelClearOrganization = $localize`:@@register.clearOrganization:Clear organization` @@ -119,7 +131,8 @@ export class FormCurrentEmploymentComponent extends BaseForm implements OnInit { private _liveAnnouncer: LiveAnnouncer, private _recordAffiliationService: RecordAffiliationService, private _formBuilder: FormBuilder, - private registerStateService: RegisterStateService + private registerStateService: RegisterStateService, + private _registerObservabilityService: RegisterObservabilityService ) { super() this._platform.get().subscribe((platform) => { @@ -127,8 +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') + .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 4aeb9f34f..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' @@ -22,6 +23,10 @@ import { OrcidValidators } from 'src/app/validators' import { BaseForm } from '../BaseForm' 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', @@ -46,7 +51,7 @@ import { environment } from 'src/environments/environment' ], 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` @@ -63,20 +68,31 @@ export class FormPasswordComponent extends BaseForm implements OnInit { hasNumberPattern = HAS_NUMBER hasLetterOrSymbolPattern = HAS_LETTER_OR_SYMBOL @Input() personalData: RegisterForm - @Input() nextButtonWasClicked: boolean + nextButtonWasClicked: boolean + currentValidate8orMoreCharactersStatus: boolean ccurentValidateAtLeastALetterOrSymbolStatus: boolean currentValidateAtLeastANumber: boolean passwordsValidAreValidAlreadyChecked: any _currentAccesibilityError: string + destroy = new Subject() constructor( private _register: Register2Service, private _liveAnnouncer: LiveAnnouncer, - private _changeDetectorRef: ChangeDetectorRef + private _changeDetectorRef: ChangeDetectorRef, + private _registerObservability: RegisterObservabilityService, + private _registerStateService: RegisterStateService ) { super() } ngOnInit() { + 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('', { @@ -212,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` ) @@ -244,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` ) @@ -295,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 dfe10074e..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' @@ -50,7 +52,9 @@ import { SignInService } from 'src/app/core/sign-in/sign-in.service' import { ErrorHandlerService } from 'src/app/core/error-handler/error-handler.service' import { ERROR_REPORT } from 'src/app/errors' import { RegisterStateService } from '../../register-state.service' -export class MyErrorStateMatcher implements ErrorStateMatcher { +import { RegisterObservabilityService } from '../../register-observability.service' +import { Subject } from 'rxjs' +export class MyErrorStateMatcher implements ErrorStateMatcher{ isErrorState( control: FormControl | null, form: FormGroupDirective | NgForm | null @@ -86,9 +90,11 @@ export class MyErrorStateMatcher implements ErrorStateMatcher { }, ], }) -export class FormPersonalComponent extends BaseForm implements OnInit { +export class FormPersonalComponent + extends BaseForm + implements OnInit, OnDestroy +{ matcher = new MyErrorStateMatcher() - @Input() nextButtonWasClicked: boolean @Input() reactivation: ReactivationLocal @ViewChild(FormGroupDirective) formGroupDir: FormGroupDirective emailPlaceholder = $localize`:@@register.emailPlaceholder:The email address you use most` @@ -104,6 +110,8 @@ export class FormPersonalComponent extends BaseForm implements OnInit { undefinedEmail: boolean emailsAreValidAlreadyChecked: boolean registerBackendErrors: RegisterBackendErrors + nextButtonWasClicked: boolean + destroy = new Subject() constructor( private _register: Register2Service, @@ -115,10 +123,14 @@ export class FormPersonalComponent extends BaseForm implements OnInit { private _signIn: SignInService, private _errorHandler: ErrorHandlerService, private _registerStateService: RegisterStateService, - @Inject(WINDOW) private window: Window + @Inject(WINDOW) private window: Window, + private _registerObservability: RegisterObservabilityService ) { super() } + ngOnDestroy(): void { + this.destroy.next() + } emails: UntypedFormGroup = new UntypedFormGroup({}) additionalEmails: UntypedFormGroup = new UntypedFormGroup({ @@ -129,6 +141,13 @@ export class FormPersonalComponent extends BaseForm implements OnInit { }) ngOnInit() { + 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('', { @@ -312,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 e12d3ffdc..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, @@ -11,6 +17,10 @@ import { VISIBILITY_OPTIONS } from 'src/app/constants' 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', @@ -36,19 +46,31 @@ import { BaseForm } from '../BaseForm' }) 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 + private _errorStateMatcher: ErrorStateMatcher, + private _registerStateService: RegisterStateService, + private _registerObservability: RegisterObservabilityService ) { super() } + ngOnDestroy(): void { + this.destroy.next() + } ngOnInit() { + 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.html b/src/app/register2/components/step-a/step-a.component.html index 4d1898211..21396af6f 100644 --- a/src/app/register2/components/step-a/step-a.component.html +++ b/src/app/register2/components/step-a/step-a.component.html @@ -50,7 +50,6 @@

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 40e5870d8..9e0d3cdbb 100644 --- a/src/app/register2/components/step-a/step-a.component.ts +++ b/src/app/register2/components/step-a/step-a.component.ts @@ -3,6 +3,7 @@ import { Component, ElementRef, Input, + OnInit, ViewChild, } from '@angular/core' @@ -13,6 +14,8 @@ import { ApplicationRoutes } from 'src/app/constants' import { environment } from 'src/environments/environment' import { ReactivationLocal } from '../../../types/reactivation.local' import { BaseStepDirective } from '../BaseStep' +import { RegisterStateService } from '../../register-state.service' +import { RegisterObservabilityService } from '../../register-observability.service' @Component({ selector: 'app-step-a', @@ -24,18 +27,30 @@ import { BaseStepDirective } from '../BaseStep' ], preserveWhitespaces: true, }) -export class StepAComponent extends BaseStepDirective implements AfterViewInit { +export class StepAComponent + extends BaseStepDirective + implements AfterViewInit, OnInit +{ @ViewChild('firstInput') firstInput: ElementRef @Input() reactivation: ReactivationLocal nextButtonWasClicked: boolean - constructor(private _platform: PlatformInfoService, private _router: Router) { + constructor( + private _platform: PlatformInfoService, + private _router: Router, + private _registerStateService: RegisterStateService, + private _registerObservabilityService: RegisterObservabilityService + ) { super() } + + ngOnInit(): void { + } infoSiteBaseUrl = environment.INFO_SITE goBack() { + this._registerStateService.registerStepperButtonClicked('a', 'back') this._platform .get() .pipe(first()) @@ -72,10 +87,11 @@ export class StepAComponent extends BaseStepDirective implements AfterViewInit { nextButton2() { this.nextButtonWasClicked = true - // this.formGroup.controls.personal.markAsTouched() + this._registerStateService.registerStepperButtonClicked('a', 'next') } signIn() { + this._registerObservabilityService.signInButtonClicked() this._platform .get() .pipe(first()) @@ -99,4 +115,8 @@ export class StepAComponent extends BaseStepDirective implements AfterViewInit { }) }) } + + backButton() { + this._registerStateService.registerStepperButtonClicked('a', 'back') + } } diff --git a/src/app/register2/components/step-b/step-b.component.html b/src/app/register2/components/step-b/step-b.component.html index 169dbafa7..bf1edfd6c 100644 --- a/src/app/register2/components/step-b/step-b.component.html +++ b/src/app/register2/components/step-b/step-b.component.html @@ -32,7 +32,6 @@

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 4060b11cc..66abd4b3f 100644 --- a/src/app/register2/components/step-c/step-c.component.ts +++ b/src/app/register2/components/step-c/step-c.component.ts @@ -1,7 +1,9 @@ -import { Component, Input } from '@angular/core' +import { Component, Input, OnInit } from '@angular/core' import { ReactivationLocal } from '../../../types/reactivation.local' import { BaseStepDirective } from '../BaseStep' +import { RegisterStateService } from '../../register-state.service' +import { RegisterObservabilityService } from '../../register-observability.service' @Component({ selector: 'app-step-c', @@ -12,11 +14,19 @@ import { BaseStepDirective } from '../BaseStep' '../register2.scss-theme.scss', ], }) -export class StepCComponent extends BaseStepDirective { +export class StepCComponent extends BaseStepDirective implements OnInit { @Input() loading @Input() reactivation: ReactivationLocal - constructor() { + constructor(private _registrationStateService: RegisterStateService) { super() } + ngOnInit(): void {} + + nextButton2() { + this._registrationStateService.registerStepperButtonClicked('c', 'next') + } + backButton() { + this._registrationStateService.registerStepperButtonClicked('c', 'back') + } } diff --git a/src/app/register2/components/step-c/step-c.spec.ts b/src/app/register2/components/step-c/step-c.spec.ts index af56bda93..36be29d63 100644 --- a/src/app/register2/components/step-c/step-c.spec.ts +++ b/src/app/register2/components/step-c/step-c.spec.ts @@ -15,6 +15,8 @@ import { RouterTestingModule } from '@angular/router/testing' import { WINDOW_PROVIDERS } from 'src/app/cdk/window' import { SnackbarService } from 'src/app/cdk/snackbar/snackbar.service' import { MatLegacySnackBarModule } from '@angular/material/legacy-snack-bar' +import { RegisterStateService } from '../../register-state.service' +import { RegisterObservabilityService } from '../../register-observability.service' @Component({ selector: 'app-form-visibility', @@ -46,8 +48,19 @@ describe('StepCComponent', () => { MatLegacySnackBarModule, ], declarations: [StepCComponent, MockFormVisibilityComponent], - providers: [WINDOW_PROVIDERS, SnackbarService], schemas: [CUSTOM_ELEMENTS_SCHEMA], + providers: [ + WINDOW_PROVIDERS, + SnackbarService, + { + provide: RegisterStateService, + useValue: {}, + }, + { + provide: RegisterObservabilityService, + useValue: {}, + }, + ], }).compileComponents() }) diff --git a/src/app/register2/components/step-c2/step-c2.component.html b/src/app/register2/components/step-c2/step-c2.component.html index a5f207a1d..025a82870 100644 --- a/src/app/register2/components/step-c2/step-c2.component.html +++ b/src/app/register2/components/step-c2/step-c2.component.html @@ -42,7 +42,6 @@

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 76f28bdae..1e679c831 100644 --- a/src/app/register2/components/step-c2/step-c2.component.ts +++ b/src/app/register2/components/step-c2/step-c2.component.ts @@ -1,8 +1,10 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core' +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' import { ReactivationLocal } from '../../../types/reactivation.local' import { BaseStepDirective } from '../BaseStep' import { FormBuilder, FormGroup } from '@angular/forms' +import { RegisterStateService } from '../../register-state.service' +import { RegisterObservabilityService } from '../../register-observability.service' @Component({ selector: 'app-step-c2', @@ -13,15 +15,16 @@ import { FormBuilder, FormGroup } from '@angular/forms' '../register2.scss-theme.scss', ], }) -export class StepC2Component extends BaseStepDirective { +export class StepC2Component extends BaseStepDirective implements OnInit { @Input() loading @Input() reactivation: ReactivationLocal nextButtonWasClicked: boolean @Output() formGroupStepC2OptionalChange = new EventEmitter() - constructor(private _formBuilder: FormBuilder) { + constructor(private _registrationStateService: RegisterStateService) { super() } + ngOnInit(): void {} optionalNextStep() { this.formGroup.setValue({ @@ -35,13 +38,16 @@ export class StepC2Component extends BaseStepDirective { }, }, }) - this.formGroupStepC2OptionalChange.emit(true) - this.nextButtonWasClicked = true + this._registrationStateService.registerStepperButtonClicked('c2', 'skip') } - requiredNextStep() { + nextButton2() { this.formGroupStepC2OptionalChange.emit(false) - this.nextButtonWasClicked = true + this._registrationStateService.registerStepperButtonClicked('c2', 'next') + } + + backButton() { + this._registrationStateService.registerStepperButtonClicked('c2', 'back') } } diff --git a/src/app/register2/components/step-c2/step-c2.spec.ts b/src/app/register2/components/step-c2/step-c2.spec.ts index 8ab14e360..49c0ef1c2 100644 --- a/src/app/register2/components/step-c2/step-c2.spec.ts +++ b/src/app/register2/components/step-c2/step-c2.spec.ts @@ -10,6 +10,8 @@ import { UntypedFormControl, UntypedFormGroup, } from '@angular/forms' +import { RegisterStateService } from '../../register-state.service' +import { RegisterObservabilityService } from '../../register-observability.service' @Component({ selector: 'app-form-current-employment', @@ -38,6 +40,16 @@ describe('StepCComponent', () => { TestBed.configureTestingModule({ imports: [ReactiveFormsModule], declarations: [StepC2Component, MockFormCurrentEmploymentComponent], + providers: [ + { + provide: RegisterStateService, + useValue: {}, + }, + { + provide: RegisterObservabilityService, + useValue: {}, + }, + ], schemas: [CUSTOM_ELEMENTS_SCHEMA], }).compileComponents() }) diff --git a/src/app/register2/components/step-d/step-d.component.html b/src/app/register2/components/step-d/step-d.component.html index afbf43998..3c441ffcc 100644 --- a/src/app/register2/components/step-d/step-d.component.html +++ b/src/app/register2/components/step-d/step-d.component.html @@ -52,7 +52,7 @@

mat-raised-button color="primary" [disabled]="loading" - (click)="nextButtonWasClicked = true" + (click)="nextButton2()" matStepperNext > matStepperPrevious type="button" id="step-d-back-button" + (click)="backButton()" > Previous Step diff --git a/src/app/register2/components/step-d/step-d.component.spec.ts b/src/app/register2/components/step-d/step-d.component.spec.ts index 3a5021278..295d19da3 100644 --- a/src/app/register2/components/step-d/step-d.component.spec.ts +++ b/src/app/register2/components/step-d/step-d.component.spec.ts @@ -10,6 +10,8 @@ import { UntypedFormControl, UntypedFormGroup, } from '@angular/forms' +import { RegisterStateService } from '../../register-state.service' +import { RegisterObservabilityService } from '../../register-observability.service' @Component({ selector: 'app-form-notifications', @@ -76,6 +78,16 @@ describe('StepDComponent', () => { MockFormAntiRobotsComponent, ], schemas: [CUSTOM_ELEMENTS_SCHEMA], + providers: [ + { + provide: RegisterStateService, + useValue: {}, + }, + { + provide: RegisterObservabilityService, + useValue: {}, + }, + ], }).compileComponents() }) 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 8f18e56db..635d02f3a 100644 --- a/src/app/register2/components/step-d/step-d.component.ts +++ b/src/app/register2/components/step-d/step-d.component.ts @@ -2,6 +2,8 @@ import { Component, Input } from '@angular/core' import { ReactivationLocal } from '../../../types/reactivation.local' import { BaseStepDirective } from '../BaseStep' +import { RegisterStateService } from '../../register-state.service' +import { RegisterObservabilityService } from '../../register-observability.service' @Component({ selector: 'app-step-d', @@ -18,7 +20,20 @@ export class StepDComponent extends BaseStepDirective { nextButtonWasClicked = false - constructor() { + constructor( + private _registrationStateService: RegisterStateService, + private _registerObservabilityService: RegisterObservabilityService + ) { super() } + ngOnInit(): void { + } + + nextButton2() { + this._registrationStateService.registerStepperButtonClicked('d', 'next') + this._registerObservabilityService.stepDNextButtonClicked(this.formGroup) + } + backButton() { + this._registrationStateService.registerStepperButtonClicked('d', 'back') + } } diff --git a/src/app/register2/pages/register/register2.component.ts b/src/app/register2/pages/register/register2.component.ts index 957449b2e..b1312b971 100644 --- a/src/app/register2/pages/register/register2.component.ts +++ b/src/app/register2/pages/register/register2.component.ts @@ -32,6 +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, + JourneyType, +} from 'src/app/core/observability-events/observability-events.service' +import { RegisterObservabilityService } from '../../register-observability.service' @Component({ selector: 'app-register-2', @@ -48,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 @@ -76,19 +83,24 @@ 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 _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() { @@ -126,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 @@ -165,12 +180,24 @@ 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._registerObservabilityService.reportRegisterErrorEvent( + 'register-validate', + { + errors: validator.errors, + } + ) } return this._register.register( this.FormGroupStepA, @@ -186,6 +213,13 @@ export class Register2Component implements OnInit, AfterViewInit { .subscribe((response) => { this.loading = false if (response.url) { + this._registerObservabilityService.reportRegisterEvent( + 'register-confirmation', + { + response, + } + ) + const analyticsReports: Observable[] = [] analyticsReports.push( @@ -208,6 +242,13 @@ export class Register2Component implements OnInit, AfterViewInit { () => this.afterRegisterRedirectionHandler(response) ) } else { + this._registerObservabilityService.reportRegisterErrorEvent( + 'register-confirmation', + { + response, + } + ) + this._errorHandler.handleError( new Error('registerUnexpectedConfirmation'), ERROR_REPORT.REGISTER @@ -241,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) } @@ -255,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 diff --git a/src/app/register2/register-observability.service.ts b/src/app/register2/register-observability.service.ts new file mode 100644 index 000000000..cc00b1655 --- /dev/null +++ b/src/app/register2/register-observability.service.ts @@ -0,0 +1,181 @@ +import { Injectable } from '@angular/core' +import { OrganizationsService } from '../core' +import { Organization } from 'src/app/types/record-affiliation.endpoint' +import { Subject } from 'rxjs' +import { OrgDisambiguated } from '../types' +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' +@Injectable({ + providedIn: 'root', +}) +export class RegisterObservabilityService { + rorIdHasBeenMatched: boolean = false + matchOrganization$ = new Subject() + primaryEmailMatched: Organization + secondaryEmail: Organization + registrationJourneyStarted: any + constructor( + private _observability: CustomEventService, + private _registrationStateService: RegisterStateService + ) { + this._registrationStateService.getBackButtonClick().subscribe((step) => { + this._observability.recordEvent( + 'orcid_registration', + `step-${step.step}-back-button-clicked` + ) + }) + } + + signInButtonClicked() { + this._observability.recordEvent( + 'orcid_registration', + 'step-a-sign-in-button-clicked' + ) + } + + stepANextButtonClicked(form: UntypedFormGroup) { + this._observability.recordEvent( + 'orcid_registration', + 'step-a-next-button-clicked', + { + // List all form controls errors + formValues: form.value, + formValid: form.valid, + errors: { + givenNamesError: form.controls.givenNames.errors, + familyNamesError: form.controls.familyNames.errors, + emailError: (form.controls.emails as UntypedFormGroup).controls.email + .errors, + confirmEmailError: (form.controls.emails as UntypedFormGroup).controls + .confirmEmail.errors, + additionalEmailsError: (form.controls.emails as UntypedFormGroup) + .controls.additionalEmails.errors, + }, + } + ) + } + + stepBNextButtonClicked(form: UntypedFormGroup) { + this._observability.recordEvent( + 'orcid_registration', + 'step-b-next-button-clicked', + { + // List all form controls errors + formValues: form.value, + formValid: form.valid, + errors: { + passwordError: form.controls.password.errors, + confirmPasswordError: form.controls.passwordConfirm.errors, + }, + } + ) + } + + stepC2NextButtonClicked(form: UntypedFormGroup) { + this._observability.recordEvent( + 'orcid_registration', + 'step-c2-next-button-clicked', + { + // List all form controls errors + formValues: form.value, + formValid: form.valid, + errors: { + departmentNameError: form.controls.departmentName.errors, + roleTitleError: form.controls.roleTitle.errors, + startDateGroupErrors: form.controls.startDateGroup.errors, + organizationErrors: form.controls.organization.errors, + }, + } + ) + } + + stepC2SkipButtonClicked(form: UntypedFormGroup) { + this._observability.recordEvent( + 'orcid_registration', + 'step-c2-skip-button-clicked', + { + // List all form controls errors + formValues: form.value, + formValid: form.valid, + errors: { + departmentNameError: form.controls.departmentName.errors, + roleTitleError: form.controls.roleTitle.errors, + startDateGroupErrors: form.controls.startDateGroup.errors, + organizationErrors: form.controls.organization.errors, + }, + } + ) + } + + stepCNextButtonClicked(form: UntypedFormGroup) { + this._observability.recordEvent( + 'orcid_registration', + 'step-c-next-button-clicked', + { + // List all form controls errors + formValues: form.value, + formValid: form.valid, + errors: { + visibilityErrors: form.controls.activitiesVisibilityDefault.errors, + }, + } + ) + } + + stepDNextButtonClicked(form: UntypedFormGroup) { + this._observability.recordEvent( + 'orcid_registration', + 'step-d-next-button-clicked', + { + // List all form controls errors + formValues: form.value, + formValid: form.valid, + errors: { + captcha: form.controls.captcha.errors, + sendMemberUpdatesError: form.controls.sendOrcidNews.errors, + termsOfUseError: form.controls.termsOfUse.errors, + }, + } + ) + } + + stepLoaded(step: JourneyType) { + this._observability.recordEvent('orcid_registration', `step-${step}-loaded`) + } + + 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 + ) + } +} diff --git a/src/app/register2/register-state.service.spec.ts b/src/app/register2/register-state.service.spec.ts index 5be2466a0..460594909 100644 --- a/src/app/register2/register-state.service.spec.ts +++ b/src/app/register2/register-state.service.spec.ts @@ -3,6 +3,7 @@ import { TestBed } from '@angular/core/testing' import { RegisterStateService } from './register-state.service' import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core' +import { OrganizationsService } from '../core' describe('RegisterStateService', () => { let service: RegisterStateService @@ -11,7 +12,7 @@ describe('RegisterStateService', () => { TestBed.configureTestingModule({ providers: [ { - provide: RegisterStateService, + provide: OrganizationsService, useValue: {}, }, ], diff --git a/src/app/register2/register-state.service.ts b/src/app/register2/register-state.service.ts index 58bf95ceb..6211e11cc 100644 --- a/src/app/register2/register-state.service.ts +++ b/src/app/register2/register-state.service.ts @@ -3,12 +3,17 @@ import { OrganizationsService } from '../core' import { Organization } from 'src/app/types/record-affiliation.endpoint' import { Subject } from 'rxjs' import { OrgDisambiguated } from '../types' +import { filter } from 'rxjs/operators' @Injectable({ providedIn: 'root', }) export class RegisterStateService { rorIdHasBeenMatched: boolean = false matchOrganization$ = new Subject() + private stepperButtonClicked = new Subject<{ + step: 'a' | 'b' | 'c' | 'c2' | 'd' + direction: 'next' | 'back' | 'skip' + }>() primaryEmailMatched: Organization secondaryEmail: Organization constructor(private _organizationsService: OrganizationsService) {} @@ -64,4 +69,32 @@ export class RegisterStateService { this.matchOrganization$.next('') } } + + registerStepperButtonClicked( + step: 'a' | 'b' | 'c' | 'c2' | 'd', + direction: 'next' | 'back' | 'skip' + ) { + this.stepperButtonClicked.next({ step, direction }) + } + + getNextButtonClickFor(step: 'a' | 'b' | 'c' | 'c2' | 'd') { + return this.stepperButtonClicked + .asObservable() + .pipe( + filter((value) => value.step === step && value.direction === 'next') + ) + } + getBackButtonClick() { + return this.stepperButtonClicked + .asObservable() + .pipe(filter((value) => value.direction === 'back')) + } + + getSkipButtonClickFor(step: 'a' | 'b' | 'c' | 'c2' | 'd') { + return this.stepperButtonClicked + .asObservable() + .pipe( + filter((value) => value.step === step && value.direction === 'skip') + ) + } } diff --git a/src/environments/environment.local.ts b/src/environments/environment.local.ts index 25061c9d3..975d5c70f 100644 --- a/src/environments/environment.local.ts +++ b/src/environments/environment.local.ts @@ -1,6 +1,6 @@ export const environment = { production: false, - debugger: false, + debugger: true, ROBOTS: 'all', API_NEWS: 'https://www.mocky.io/v2/5dced45b3000007300931ce8', API_PUB: `//pub./v3.0`,