From dc7e69e1ccf284dd8581956996f25e0d46931b44 Mon Sep 17 00:00:00 2001 From: malincrist <92857141+malincrist@users.noreply.github.com> Date: Tue, 20 Feb 2024 15:19:21 +0100 Subject: [PATCH] Improved logging around AAD B2C flows (#2402) * Improved logging around AAD B2C flows * ran lint:fix --- src/admin/custom.d.ts | 4 +- src/admin/utils/helpers.ts | 10 +- src/admin/utils/themes.ts | 114 +++++++++--------- src/admin/utils/validator.ts | 26 ++-- src/apim.runtime.module.ts | 2 + src/bindingHandlers/traceClick.ts | 18 +++ .../ko/runtime/api-list-dropdown.ts | 8 +- .../ko/runtime/operation-list.ts | 2 +- .../ko/runtime/change-password.ts | 2 +- .../ko/runtime/confirm-password.ts | 6 +- .../users/profile/ko/runtime/profile.ts | 6 +- .../ko/runtime/reset-password.ts | 2 +- .../ko/runtime/signin-aad-b2c.html | 2 +- .../ko/runtime/signin-aad-b2c.ts | 16 ++- .../signin-social/ko/runtime/signin-aad.html | 2 +- .../signin-social/ko/runtime/signin-aad.ts | 15 ++- .../users/signin/ko/runtime/signin.html | 2 +- .../ko/runtime/signup-social.html | 2 +- .../signup-social/ko/runtime/signup-social.ts | 9 +- .../users/signup/ko/runtime/signup.ts | 2 +- .../subscriptions/ko/runtime/subscriptions.ts | 14 +-- .../users/validation-summary/utils.ts | 11 +- src/logging/clientLogger.ts | 11 +- src/services/aadServiceV2.ts | 18 ++- src/services/usersService.ts | 6 +- src/startup.runtime.ts | 5 + 26 files changed, 197 insertions(+), 118 deletions(-) create mode 100644 src/bindingHandlers/traceClick.ts diff --git a/src/admin/custom.d.ts b/src/admin/custom.d.ts index 436ba68a4..f20c3e649 100644 --- a/src/admin/custom.d.ts +++ b/src/admin/custom.d.ts @@ -1,5 +1,5 @@ -declare module '*.svg' { - import React = require('react'); +declare module "*.svg" { + import React = require("react"); export const ReactComponent: React.FC>; const src: string; export default src; diff --git a/src/admin/utils/helpers.ts b/src/admin/utils/helpers.ts index 33284e4d1..7c5fd334d 100644 --- a/src/admin/utils/helpers.ts +++ b/src/admin/utils/helpers.ts @@ -1,15 +1,15 @@ -import * as MediaUtils from '@paperbits/common/media/mediaUtils'; -import { MediaContract } from '@paperbits/common/media'; +import * as MediaUtils from "@paperbits/common/media/mediaUtils"; +import { MediaContract } from "@paperbits/common/media"; export const getThumbnailUrl = (mediaItem: MediaContract): string => { - if (mediaItem?.mimeType?.startsWith('video')) { - let thumbnailUrl: string = ''; + if (mediaItem?.mimeType?.startsWith("video")) { + let thumbnailUrl: string = ""; MediaUtils.getVideoThumbnailAsDataUrlFromUrl(mediaItem.downloadUrl).then(result => thumbnailUrl = result); return thumbnailUrl; } - if (mediaItem?.mimeType?.startsWith('image')) { + if (mediaItem?.mimeType?.startsWith("image")) { let thumbnailUrl = mediaItem.downloadUrl; if (mediaItem.variants) { diff --git a/src/admin/utils/themes.ts b/src/admin/utils/themes.ts index 1684ea377..782d5a7cd 100644 --- a/src/admin/utils/themes.ts +++ b/src/admin/utils/themes.ts @@ -1,4 +1,4 @@ -import { PartialTheme } from '@fluentui/react'; +import { PartialTheme } from "@fluentui/react"; /** * Custom Fluent theme pallete used by calling related components in this library. @@ -22,38 +22,38 @@ export interface CallingTheme { */ export const lightTheme: PartialTheme & CallingTheme = { palette: { - themePrimary: '#0078d4', - themeLighterAlt: '#eff6fc', - themeLighter: '#deecf9', - themeLight: '#c7e0f4', - themeTertiary: '#71afe5', - themeSecondary: '#2b88d8', - themeDarkAlt: '#106ebe', - themeDark: '#59b0f7', - themeDarker: '#004578', - neutralLighterAlt: '#faf9f8', - neutralLighter: '#f3f2f1', - neutralLight: '#edebe9', - neutralQuaternaryAlt: '#e1dfdd', - neutralQuaternary: '#d0d0d0', - neutralTertiaryAlt: '#c8c6c4', - neutralTertiary: '#a19f9d', - neutralSecondary: '#605e5c', - neutralPrimaryAlt: '#3b3a39', - neutralPrimary: '#323130', - neutralDark: '#201f1e', - black: '#000000', - white: '#ffffff' + themePrimary: "#0078d4", + themeLighterAlt: "#eff6fc", + themeLighter: "#deecf9", + themeLight: "#c7e0f4", + themeTertiary: "#71afe5", + themeSecondary: "#2b88d8", + themeDarkAlt: "#106ebe", + themeDark: "#59b0f7", + themeDarker: "#004578", + neutralLighterAlt: "#faf9f8", + neutralLighter: "#f3f2f1", + neutralLight: "#edebe9", + neutralQuaternaryAlt: "#e1dfdd", + neutralQuaternary: "#d0d0d0", + neutralTertiaryAlt: "#c8c6c4", + neutralTertiary: "#a19f9d", + neutralSecondary: "#605e5c", + neutralPrimaryAlt: "#3b3a39", + neutralPrimary: "#323130", + neutralDark: "#201f1e", + black: "#000000", + white: "#ffffff" }, callingPalette: { - callRed: '#a42e43', - callRedDark: '#8b2c3d', - callRedDarker: '#772a38', - iconWhite: '#ffffff', - green: '#107c10' + callRed: "#a42e43", + callRedDark: "#8b2c3d", + callRedDarker: "#772a38", + iconWhite: "#ffffff", + green: "#107c10" }, semanticColors: { - errorText: '#a80000' + errorText: "#a80000" } }; @@ -64,37 +64,37 @@ export const lightTheme: PartialTheme & CallingTheme = { */ export const darkTheme: PartialTheme & CallingTheme = { palette: { - themePrimary: '#2899f5', - themeLighterAlt: '#02060a', - themeLighter: '#061827', - themeLight: '#0c2e49', - themeTertiary: '#185b93', - themeSecondary: '#2286d7', - themeDarkAlt: '#3ca2f6', - themeDark: '#59b0f7', - themeDarker: '#84c5f9', - neutralLighterAlt: '#302e2d', - neutralLighter: '#383735', - neutralLight: '#464443', - neutralQuaternaryAlt: '#4e4d4b', - neutralQuaternary: '#4d4b49', - neutralTertiaryAlt: '#72706e', - neutralTertiary: '#c8c8c8', - neutralSecondary: '#d0d0d0', - neutralPrimaryAlt: '#dadada', - neutralPrimary: '#ffffff', - neutralDark: '#f4f4f4', - black: '#f8f8f8', - white: '#252423' + themePrimary: "#2899f5", + themeLighterAlt: "#02060a", + themeLighter: "#061827", + themeLight: "#0c2e49", + themeTertiary: "#185b93", + themeSecondary: "#2286d7", + themeDarkAlt: "#3ca2f6", + themeDark: "#59b0f7", + themeDarker: "#84c5f9", + neutralLighterAlt: "#302e2d", + neutralLighter: "#383735", + neutralLight: "#464443", + neutralQuaternaryAlt: "#4e4d4b", + neutralQuaternary: "#4d4b49", + neutralTertiaryAlt: "#72706e", + neutralTertiary: "#c8c8c8", + neutralSecondary: "#d0d0d0", + neutralPrimaryAlt: "#dadada", + neutralPrimary: "#ffffff", + neutralDark: "#f4f4f4", + black: "#f8f8f8", + white: "#252423" }, callingPalette: { - callRed: '#c4314b', - callRedDark: '#a42e43', - callRedDarker: '#8b2c3d', - iconWhite: '#ffffff', - green: '#107c10' + callRed: "#c4314b", + callRedDark: "#a42e43", + callRedDarker: "#8b2c3d", + iconWhite: "#ffffff", + green: "#107c10" }, semanticColors: { - errorText: '#f1707b' + errorText: "#f1707b" } }; \ No newline at end of file diff --git a/src/admin/utils/validator.ts b/src/admin/utils/validator.ts index 8adbded01..d7912253c 100644 --- a/src/admin/utils/validator.ts +++ b/src/admin/utils/validator.ts @@ -1,18 +1,18 @@ -export const REQUIRED = 'required'; -export const UNIQUE_REQUIRED = 'unique-required'; -export const URL = 'url'; -export const URL_REQUIRED = 'url-required'; +export const REQUIRED = "required"; +export const UNIQUE_REQUIRED = "unique-required"; +export const URL = "url"; +export const URL_REQUIRED = "url-required"; -export const REQUIRED_MESSAGE = 'This field is required'; -export const UNIQUE_REQUIRED_MESSAGE = 'Field value is required and must be unique'; -export const URL_MESSAGE = 'Field value should be a valid URL'; -export const URL_REQUIRED_MESSAGE = 'Field value is required and should be a valid URL'; +export const REQUIRED_MESSAGE = "This field is required"; +export const UNIQUE_REQUIRED_MESSAGE = "Field value is required and must be unique"; +export const URL_MESSAGE = "Field value should be a valid URL"; +export const URL_REQUIRED_MESSAGE = "Field value is required and should be a valid URL"; export const validateField = (validationType: string, value: string, customValidation?: boolean): string => { if (value === undefined) return; let isValid: boolean = true; - let errorMessage: string = ''; + let errorMessage: string = ""; const absoluteUrlRegex = /^(?:https?:\/\/)?(?:[\w-]+\.)*[\w.-]+\.[a-zA-Z]{2,}(?:\/[\w-.~:/?#[\]@!$&'()*+,;=%]*)?$/; const relativeUrlRegex = /^(?:\/|#)[\w-.~:/?#[\]@!$&'()*+,;=%]*$/; @@ -20,19 +20,19 @@ export const validateField = (validationType: string, value: string, customValid switch (validationType) { case REQUIRED: isValid = value.length > 0; - errorMessage = isValid ? '' : REQUIRED_MESSAGE; + errorMessage = isValid ? "" : REQUIRED_MESSAGE; break; case UNIQUE_REQUIRED: isValid = value.length > 0 && customValidation; - errorMessage = isValid ? '' : UNIQUE_REQUIRED_MESSAGE; + errorMessage = isValid ? "" : UNIQUE_REQUIRED_MESSAGE; break; case URL: isValid = value.length === 0 || (absoluteUrlRegex.test(value) || relativeUrlRegex.test(value)); - errorMessage = isValid ? '' : URL_MESSAGE; + errorMessage = isValid ? "" : URL_MESSAGE; break; case URL_REQUIRED: isValid = value.length > 0 && (absoluteUrlRegex.test(value) || relativeUrlRegex.test(value)); - errorMessage = isValid ? '' : URL_REQUIRED_MESSAGE; + errorMessage = isValid ? "" : URL_REQUIRED_MESSAGE; break; } diff --git a/src/apim.runtime.module.ts b/src/apim.runtime.module.ts index f928efcef..9edd1c442 100644 --- a/src/apim.runtime.module.ts +++ b/src/apim.runtime.module.ts @@ -87,11 +87,13 @@ import { StaticDataHttpClient } from "./services/staticDataHttpClient"; import { TagService } from "./services/tagService"; import { TenantService } from "./services/tenantService"; import { UsersService } from "./services/usersService"; +import { TraceClick } from "./bindingHandlers/traceClick"; export class ApimRuntimeModule implements IInjectorModule { public register(injector: IInjector): void { injector.bindSingleton("logger", ConsoleLogger); + injector.bindSingleton("traceClick", TraceClick); injector.bindToCollection("autostart", UnhandledErrorHandler); injector.bindToCollection("autostart", BalloonBindingHandler); injector.bindToCollection("autostart", ResizableBindingHandler); diff --git a/src/bindingHandlers/traceClick.ts b/src/bindingHandlers/traceClick.ts new file mode 100644 index 000000000..d7722ba04 --- /dev/null +++ b/src/bindingHandlers/traceClick.ts @@ -0,0 +1,18 @@ +import { Logger } from "@paperbits/common/logging"; +import * as ko from "knockout"; +import { eventTypes } from "../logging/clientLogger"; + +export class TraceClick { + constructor(private readonly logger: Logger) { + } + + public setupBinding(): void { + ko.bindingHandlers["traceClick"] = { + init: (element: HTMLElement): void => { + ko.utils.registerEventHandler(element, "click", () => { + this.logger.trackEvent(eventTypes.click, { message: `User clicked on the element with id ${element.id ?? "-"}` }); + }); + } + } + } +} \ No newline at end of file diff --git a/src/components/apis/list-of-apis/ko/runtime/api-list-dropdown.ts b/src/components/apis/list-of-apis/ko/runtime/api-list-dropdown.ts index 8f2d59944..f537b6535 100644 --- a/src/components/apis/list-of-apis/ko/runtime/api-list-dropdown.ts +++ b/src/components/apis/list-of-apis/ko/runtime/api-list-dropdown.ts @@ -185,10 +185,10 @@ export class ApiListDropdown { } public closeDropdown(): void { - const apiDropdowns = document.getElementsByClassName('api-list-dropdown'); - for (var i = 0; i < apiDropdowns.length; i++) { - if (apiDropdowns[i].classList.contains('show')) - apiDropdowns[i].classList.remove('show'); + const apiDropdowns = document.getElementsByClassName("api-list-dropdown"); + for (let i = 0; i < apiDropdowns.length; i++) { + if (apiDropdowns[i].classList.contains("show")) + apiDropdowns[i].classList.remove("show"); } } diff --git a/src/components/operations/operation-list/ko/runtime/operation-list.ts b/src/components/operations/operation-list/ko/runtime/operation-list.ts index aba0d59bf..188518066 100644 --- a/src/components/operations/operation-list/ko/runtime/operation-list.ts +++ b/src/components/operations/operation-list/ko/runtime/operation-list.ts @@ -119,7 +119,7 @@ export class OperationList { .subscribe(this.loadOperations); if (this.defaultAllGroupTagsExpanded()) { - let groups = new Set() + const groups = new Set() this.operationGroups().map(g => {groups.add(g.tag)}) this.groupTagsExpanded(groups); } diff --git a/src/components/users/change-password/ko/runtime/change-password.ts b/src/components/users/change-password/ko/runtime/change-password.ts index 60adb7d10..6e59d7ffb 100644 --- a/src/components/users/change-password/ko/runtime/change-password.ts +++ b/src/components/users/change-password/ko/runtime/change-password.ts @@ -148,7 +148,7 @@ export class ChangePassword { await this.refreshCaptcha(); } - parseAndDispatchError(this.eventManager, ErrorSources.changepassword, error, undefined, detail => `${detail.target}: ${detail.message} \n`); + parseAndDispatchError(this.eventManager, ErrorSources.changepassword, error, this.logger, undefined, detail => `${detail.target}: ${detail.message} \n`); } finally { this.working(false); } diff --git a/src/components/users/confirm-password/ko/runtime/confirm-password.ts b/src/components/users/confirm-password/ko/runtime/confirm-password.ts index fc833e729..fbefbd831 100644 --- a/src/components/users/confirm-password/ko/runtime/confirm-password.ts +++ b/src/components/users/confirm-password/ko/runtime/confirm-password.ts @@ -7,6 +7,7 @@ import { UsersService } from "../../../../../services"; import { ErrorSources } from "../../../validation-summary/constants"; import { dispatchErrors, parseAndDispatchError } from "../../../validation-summary/utils"; import { ValidationMessages } from "../../../validationMessages"; +import { Logger } from "@paperbits/common/logging"; @RuntimeComponent({ selector: "confirm-password" @@ -25,7 +26,8 @@ export class ConfirmPassword { constructor( private readonly usersService: UsersService, - private readonly eventManager: EventManager) { + private readonly eventManager: EventManager, + private readonly logger: Logger) { this.password = ko.observable(); this.passwordConfirmation = ko.observable(); this.isResetConfirmed = ko.observable(false); @@ -104,7 +106,7 @@ export class ConfirmPassword { }, 1000); } catch (error) { - parseAndDispatchError(this.eventManager, ErrorSources.confirmpassword, error, undefined, detail => `${detail.target}: ${detail.message} \\n`); + parseAndDispatchError(this.eventManager, ErrorSources.confirmpassword, error, this.logger, undefined, detail => `${detail.target}: ${detail.message} \\n`); } } } diff --git a/src/components/users/profile/ko/runtime/profile.ts b/src/components/users/profile/ko/runtime/profile.ts index 455db4813..323443ebb 100644 --- a/src/components/users/profile/ko/runtime/profile.ts +++ b/src/components/users/profile/ko/runtime/profile.ts @@ -15,6 +15,7 @@ import { dispatchErrors, parseAndDispatchError } from "../../../validation-summa import { ErrorSources } from "../../../validation-summary/constants"; import { BackendService } from "../../../../../services/backendService"; import { ValidationMessages } from "../../../validationMessages"; +import { Logger } from "@paperbits/common/logging"; @RuntimeComponent({ selector: "profile-runtime" @@ -40,7 +41,8 @@ export class Profile { private readonly tenantService: TenantService, private readonly backendService: BackendService, private readonly eventManager: EventManager, - private readonly router: Router) { + private readonly router: Router, + private readonly logger: Logger) { this.user = ko.observable(); this.firstName = ko.observable(); this.lastName = ko.observable(); @@ -133,7 +135,7 @@ export class Profile { this.setUser(user); await this.toggleEdit(); } catch (error) { - parseAndDispatchError(this.eventManager, ErrorSources.changeProfile, error); + parseAndDispatchError(this.eventManager, ErrorSources.changeProfile, error, this.logger); } this.working(false); } diff --git a/src/components/users/reset-password/ko/runtime/reset-password.ts b/src/components/users/reset-password/ko/runtime/reset-password.ts index d1426a334..de726fe0e 100644 --- a/src/components/users/reset-password/ko/runtime/reset-password.ts +++ b/src/components/users/reset-password/ko/runtime/reset-password.ts @@ -128,7 +128,7 @@ export class ResetPassword { await this.refreshCaptcha(); } - parseAndDispatchError(this.eventManager, ErrorSources.resetpassword, error, undefined, detail => `${detail.target}: ${detail.message} \n`); + parseAndDispatchError(this.eventManager, ErrorSources.resetpassword, error, this.logger, undefined, detail => `${detail.target}: ${detail.message} \n`); } finally { this.working(false); diff --git a/src/components/users/signin-social/ko/runtime/signin-aad-b2c.html b/src/components/users/signin-social/ko/runtime/signin-aad-b2c.html index 86ea2fccd..c524e6f85 100644 --- a/src/components/users/signin-social/ko/runtime/signin-aad-b2c.html +++ b/src/components/users/signin-social/ko/runtime/signin-aad-b2c.html @@ -1,4 +1,4 @@ - diff --git a/src/components/users/signin-social/ko/runtime/signin-aad-b2c.ts b/src/components/users/signin-social/ko/runtime/signin-aad-b2c.ts index 1c8559157..b34308a64 100644 --- a/src/components/users/signin-social/ko/runtime/signin-aad-b2c.ts +++ b/src/components/users/signin-social/ko/runtime/signin-aad-b2c.ts @@ -8,6 +8,8 @@ import { AadB2CClientConfig } from "../../../../../contracts/aadB2CClientConfig" import { AadService, AadServiceV2, IAadService } from "../../../../../services"; import { dispatchErrors, parseAndDispatchError } from "../../../validation-summary/utils"; import { ErrorSources } from "../../../validation-summary/constants"; +import { Logger } from "@paperbits/common/logging"; +import { eventTypes } from "../../../../../logging/clientLogger"; const aadb2cResetPasswordErrorCode = "AADB2C90118"; @@ -27,7 +29,8 @@ export class SignInAadB2C { private readonly aadService: AadService, private readonly aadServiceV2: AadServiceV2, private readonly eventManager: EventManager, - private readonly settingsProvider: ISettingsProvider + private readonly settingsProvider: ISettingsProvider, + private readonly logger: Logger ) { this.classNames = ko.observable(); this.label = ko.observable(); @@ -44,7 +47,7 @@ export class SignInAadB2C { @Param() public replyUrl: ko.Observable; - + @Param() public termsOfUse: ko.Observable; @@ -60,15 +63,22 @@ export class SignInAadB2C { } await this.selectedService.checkCallbacks(); } + else { + this.logger.trackEvent(eventTypes.aadB2CLogin, { message: "AAD B2C client configuration is missing." }); + } + + this.logger.trackEvent(eventTypes.aadB2CLogin, { message: "Signin AAD B2C component initialized." }); } /** * Initiates signing-in with Azure Active Directory B2C. */ public async signIn(): Promise { + this.logger.trackEvent(eventTypes.aadB2CLogin, { message: "Login initiated." }); this.cleanValidationErrors(); if (!this.aadConfig) { + this.logger.trackEvent(eventTypes.aadB2CLogin, { message: "AAD B2C client configuration is missing." }) return; } @@ -91,7 +101,7 @@ export class SignInAadB2C { } } - parseAndDispatchError(this.eventManager, ErrorSources.signInOAuth, error); + parseAndDispatchError(this.eventManager, ErrorSources.signInOAuth, error, this.logger); } } diff --git a/src/components/users/signin-social/ko/runtime/signin-aad.html b/src/components/users/signin-social/ko/runtime/signin-aad.html index ce0edbcc6..ae1195116 100644 --- a/src/components/users/signin-social/ko/runtime/signin-aad.html +++ b/src/components/users/signin-social/ko/runtime/signin-aad.html @@ -1,4 +1,4 @@ - diff --git a/src/components/users/signin-social/ko/runtime/signin-aad.ts b/src/components/users/signin-social/ko/runtime/signin-aad.ts index 66cf72238..895a45b95 100644 --- a/src/components/users/signin-social/ko/runtime/signin-aad.ts +++ b/src/components/users/signin-social/ko/runtime/signin-aad.ts @@ -8,6 +8,8 @@ import { AadClientLibrary, SettingNames, defaultAadTenantName } from "../../../. import { AadClientConfig } from "../../../../../contracts/aadClientConfig"; import { dispatchErrors, parseAndDispatchError } from "../../../validation-summary/utils"; import { ErrorSources } from "../../../validation-summary/constants"; +import { Logger } from "@paperbits/common/logging"; +import { eventTypes } from "../../../../../logging/clientLogger"; @RuntimeComponent({ selector: "signin-aad" @@ -23,7 +25,8 @@ export class SignInAad { private readonly aadService: AadService, private readonly aadServiceV2: AadServiceV2, private readonly eventManager: EventManager, - private readonly settingsProvider: ISettingsProvider + private readonly settingsProvider: ISettingsProvider, + private readonly logger: Logger ) { this.classNames = ko.observable(); this.label = ko.observable(); @@ -49,20 +52,26 @@ export class SignInAad { */ public async signIn(): Promise { dispatchErrors(this.eventManager, ErrorSources.signInOAuth, []); + this.logger.trackEvent(eventTypes.aadLogin, { message: "Initiating AAD login" }); + try { const config = await this.settingsProvider.getSetting(SettingNames.aadClientConfig); if (config) { if (config.clientLibrary === AadClientLibrary.v2) { this.selectedService = this.aadServiceV2; - } else { + } + else { this.selectedService = this.aadService; } await this.selectedService.signInWithAad(config.clientId, config.authority, config.signinTenant || defaultAadTenantName, this.replyUrl()); } + else { + this.logger.trackEvent(eventTypes.aadLogin, { message: "AAD client config is not set" }); + } } catch (error) { - parseAndDispatchError(this.eventManager, ErrorSources.signInOAuth, error); + parseAndDispatchError(this.eventManager, ErrorSources.signInOAuth, error, this.logger); } } } diff --git a/src/components/users/signin/ko/runtime/signin.html b/src/components/users/signin/ko/runtime/signin.html index f55f44bc4..f011605db 100644 --- a/src/components/users/signin/ko/runtime/signin.html +++ b/src/components/users/signin/ko/runtime/signin.html @@ -15,7 +15,7 @@
-
diff --git a/src/components/users/signup-social/ko/runtime/signup-social.html b/src/components/users/signup-social/ko/runtime/signup-social.html index 48f05fe11..98898796a 100644 --- a/src/components/users/signup-social/ko/runtime/signup-social.html +++ b/src/components/users/signup-social/ko/runtime/signup-social.html @@ -21,7 +21,7 @@
-
diff --git a/src/components/users/signup-social/ko/runtime/signup-social.ts b/src/components/users/signup-social/ko/runtime/signup-social.ts index f7a122f72..1715dbcf6 100644 --- a/src/components/users/signup-social/ko/runtime/signup-social.ts +++ b/src/components/users/signup-social/ko/runtime/signup-social.ts @@ -11,6 +11,8 @@ import { UsersService } from "../../../../../services"; import { dispatchErrors, parseAndDispatchError } from "../../../validation-summary/utils"; import { ErrorSources } from "../../../validation-summary/constants"; import { ValidationMessages } from "../../../validationMessages"; +import { Logger } from "@paperbits/common/logging"; +import { eventTypes } from "../../../../../logging/clientLogger"; @RuntimeComponent({ @@ -31,7 +33,8 @@ export class SignupSocial { private readonly eventManager: EventManager, private readonly router: Router, private readonly routeHelper: RouteHelper, - private readonly usersService: UsersService + private readonly usersService: UsersService, + private readonly logger: Logger ) { this.email = ko.observable(""); this.firstName = ko.observable(""); @@ -81,6 +84,8 @@ export class SignupSocial { this.firstName(jwtToken.given_name); this.lastName(jwtToken.family_name); this.email(jwtToken.email || jwtToken.emails?.[0]); + + this.logger.trackEvent(eventTypes.trace, { message: "Signup social component initialized." }); } /** @@ -120,7 +125,7 @@ export class SignupSocial { await this.usersService.createUserWithOAuth(provider, idToken, this.firstName(), this.lastName(), this.email()); await this.router.navigateTo(Constants.pageUrlHome); } catch (error) { - parseAndDispatchError(this.eventManager, ErrorSources.signup, error, Constants.genericHttpRequestError); + parseAndDispatchError(this.eventManager, ErrorSources.signup, error, this.logger, Constants.genericHttpRequestError); } } } diff --git a/src/components/users/signup/ko/runtime/signup.ts b/src/components/users/signup/ko/runtime/signup.ts index 9c2207d1e..6d626f97e 100644 --- a/src/components/users/signup/ko/runtime/signup.ts +++ b/src/components/users/signup/ko/runtime/signup.ts @@ -214,7 +214,7 @@ export class Signup { await this.refreshCaptcha(); } - parseAndDispatchError(this.eventManager, ErrorSources.signup, error, Constants.genericHttpRequestError); + parseAndDispatchError(this.eventManager, ErrorSources.signup, error, this.logger, Constants.genericHttpRequestError); } finally { this.working(false); diff --git a/src/components/users/subscriptions/ko/runtime/subscriptions.ts b/src/components/users/subscriptions/ko/runtime/subscriptions.ts index 85889946c..b294299d3 100644 --- a/src/components/users/subscriptions/ko/runtime/subscriptions.ts +++ b/src/components/users/subscriptions/ko/runtime/subscriptions.ts @@ -8,13 +8,13 @@ import { ProductService } from "../../../../../services/productService"; import { TenantService } from "../../../../../services/tenantService"; import { DelegationParameters, DelegationAction } from "../../../../../contracts/tenantSettings"; import { Utils } from "../../../../../utils"; -import { Router } from "@paperbits/common/routing/router"; import { EventManager } from "@paperbits/common/events"; import { dispatchErrors, parseAndDispatchError } from "../../../validation-summary/utils"; import { ErrorSources } from "../../../validation-summary/constants"; import { BackendService } from "../../../../../services/backendService"; import { SearchQuery } from "../../../../../contracts/searchQuery"; import * as Constants from "../../../../../constants"; +import { Logger } from "@paperbits/common/logging"; @RuntimeComponent({ selector: "subscriptions-runtime" @@ -34,9 +34,9 @@ export class Subscriptions { private readonly usersService: UsersService, private readonly tenantService: TenantService, private readonly backendService: BackendService, - private readonly router: Router, private readonly productService: ProductService, - private readonly eventManager: EventManager + private readonly eventManager: EventManager, + private readonly logger: Logger ) { this.subscriptions = ko.observableArray(); this.pageNumber = ko.observable(1); @@ -98,7 +98,7 @@ export class Subscriptions { this.subscriptions.replace(subscription, updatedVM); subscription.toggleEdit(); } catch (error) { - parseAndDispatchError(this.eventManager, ErrorSources.renameSubscription, error); + parseAndDispatchError(this.eventManager, ErrorSources.renameSubscription, error, this.logger); } } @@ -112,7 +112,7 @@ export class Subscriptions { updatedVM.changedItem("primaryKey"); this.subscriptions.replace(subscription, updatedVM); } catch (error) { - parseAndDispatchError(this.eventManager, ErrorSources.regeneratePKey, error); + parseAndDispatchError(this.eventManager, ErrorSources.regeneratePKey, error, this.logger); } subscription.isPRegenerating(false); } @@ -127,7 +127,7 @@ export class Subscriptions { updatedVM.changedItem("secondaryKey"); this.subscriptions.replace(subscription, updatedVM); } catch (error) { - parseAndDispatchError(this.eventManager, ErrorSources.regenerateSKey, error); + parseAndDispatchError(this.eventManager, ErrorSources.regenerateSKey, error, this.logger); } subscription.isSRegenerating(false); } @@ -166,7 +166,7 @@ export class Subscriptions { return; } - parseAndDispatchError(this.eventManager, ErrorSources.cancelSubscription, error); + parseAndDispatchError(this.eventManager, ErrorSources.cancelSubscription, error, this.logger); } finally { subscription.isSRegenerating(false); } diff --git a/src/components/users/validation-summary/utils.ts b/src/components/users/validation-summary/utils.ts index 02b5e2f32..99b34f87f 100644 --- a/src/components/users/validation-summary/utils.ts +++ b/src/components/users/validation-summary/utils.ts @@ -2,17 +2,20 @@ import { EventManager } from "@paperbits/common/events"; import { ValidationReport } from "../../../contracts/validationReport"; import { MapiError } from "../../../errors/mapiError"; import { ErrorSources, onValidationErrors } from "./constants"; +import { Logger } from "@paperbits/common/logging"; +import { eventTypes } from "../../../logging/clientLogger"; export function parseAndDispatchError( eventManager: EventManager, source: ErrorSources, - error: MapiError, + error: Error, + logger: Logger, defaultMessage?: string, errorDetailsMap: (detail: any) => string = detail => `${detail.message}` ): string[] { let errorDetails: string[]; - if (error.code === "ValidationError" && error.details?.length > 0) { + if (error instanceof MapiError && error.code === "ValidationError" && error.details?.length > 0) { errorDetails = error.details.map(errorDetailsMap); // Prioritize errors from the error.details object. } else if (error.message) { errorDetails = [defaultMessage ?? error.message]; @@ -23,12 +26,12 @@ export function parseAndDispatchError( } dispatchErrors(eventManager, source, errorDetails); - + logger.trackEvent(eventTypes.userError, { message: `Dispatched error from ${source}: ${error?.message}` }); return errorDetails; } export function dispatchErrors(eventManager: EventManager, source: ErrorSources, errors: string[]): void { - dispatchValidationReport(eventManager, {source, errors}); + dispatchValidationReport(eventManager, { source, errors }); } export function dispatchValidationReport(eventManager: EventManager, validationReport: ValidationReport): void { diff --git a/src/logging/clientLogger.ts b/src/logging/clientLogger.ts index d3de77023..d29fde865 100644 --- a/src/logging/clientLogger.ts +++ b/src/logging/clientLogger.ts @@ -6,6 +6,15 @@ import { ClientEvent } from "../models/logging/clientEvent"; import { v4 as uuidv4 } from "uuid"; import * as Constants from "../constants"; +export enum eventTypes { + error = "Error", + userError = "UserError", + trace = "Trace", + aadB2CLogin = "AadB2CLogin", + aadLogin = "AadLogin", + click = "Click", +} + export class ClientLogger implements Logger { private clientVersion: string; private backendUrl: string; @@ -35,7 +44,7 @@ export class ClientLogger implements Logger { public async trackError(error: Error, properties?: Bag): Promise { const devPortalEvent = new ClientEvent(); - devPortalEvent.eventType = "Error"; + devPortalEvent.eventType = eventTypes.error; devPortalEvent.message = error?.message; devPortalEvent.eventData = JSON.stringify(properties); devPortalEvent.exception = error?.stack; diff --git a/src/services/aadServiceV2.ts b/src/services/aadServiceV2.ts index e8b9f641d..928f05d2a 100644 --- a/src/services/aadServiceV2.ts +++ b/src/services/aadServiceV2.ts @@ -6,6 +6,8 @@ import { RouteHelper } from "../routing/routeHelper"; import { Utils } from "../utils"; import { UsersService } from "./usersService"; import { IAadService } from "./IAadService"; +import { Logger } from "@paperbits/common/logging"; +import { eventTypes } from "../logging/clientLogger"; /** * Service for operations with Azure Active Directory identity provider. @@ -15,7 +17,8 @@ export class AadServiceV2 implements IAadService { private readonly router: Router, private readonly routeHelper: RouteHelper, private readonly usersService: UsersService, - private readonly httpClient: HttpClient + private readonly httpClient: HttpClient, + private readonly logger: Logger ) { } /** @@ -23,11 +26,12 @@ export class AadServiceV2 implements IAadService { * @param {string} idToken - ID token. * @param {string} provider - Provider type, `Aad` or `AadB2C`. */ - private async exchangeIdToken(idToken: string, provider: string): Promise { + private async exchangeIdToken(idToken: string, provider: string, calledFrom: eventTypes): Promise { const credentials = `${provider} id_token="${idToken}"`; const userId = await this.usersService.authenticate(credentials); if (!userId) { // User not registered with APIM. + this.logger.trackEvent(calledFrom, { message: "User not registered with APIM. Starting signup flow." }); const jwtToken = Utils.parseJwt(idToken); const firstName = jwtToken.given_name; const lastName = jwtToken.family_name; @@ -37,6 +41,7 @@ export class AadServiceV2 implements IAadService { await this.usersService.createUserWithOAuth(provider, idToken, firstName, lastName, email); } else { + this.logger.trackEvent(calledFrom, { message: "User data not found in ID token. Navigating to signup URL." }); const signupUrl = this.routeHelper.getIdTokenReferenceUrl(provider, idToken); await this.router.navigateTo(signupUrl); return; @@ -102,7 +107,8 @@ export class AadServiceV2 implements IAadService { const response = await msalInstance.loginPopup(loginRequest); if (response.idToken) { - await this.exchangeIdToken(response.idToken, Constants.IdentityProviders.aad); + await this.exchangeIdToken(response.idToken, Constants.IdentityProviders.aad, eventTypes.aadLogin); + this.logger.trackEvent(eventTypes.aadLogin, { message: "Login successful." }); } } @@ -155,7 +161,11 @@ export class AadServiceV2 implements IAadService { const response = await msalInstance.loginPopup(loginRequest); if (response.idToken) { - await this.exchangeIdToken(response.idToken, Constants.IdentityProviders.aadB2C); + await this.exchangeIdToken(response.idToken, Constants.IdentityProviders.aadB2C, eventTypes.aadB2CLogin); + this.logger.trackEvent(eventTypes.aadB2CLogin, { message: "Login successful." }); + } + else { + this.logger.trackEvent(eventTypes.aadB2CLogin, { message: "AadB2C Login failed: ID token not found in response." }); } } diff --git a/src/services/usersService.ts b/src/services/usersService.ts index 3c2cca2fd..ecb4283bf 100644 --- a/src/services/usersService.ts +++ b/src/services/usersService.ts @@ -14,6 +14,8 @@ import { MapiSignupRequest } from "../contracts/signupRequest"; import { MapiError } from "../errors/mapiError"; import { KnownMimeTypes } from "../models/knownMimeTypes"; import { UnauthorizedError } from "../errors/unauthorizedError"; +import { Logger } from "@paperbits/common/logging"; +import { eventTypes } from "../logging/clientLogger"; /** * A service for management operations with users. @@ -24,7 +26,8 @@ export class UsersService { private readonly router: Router, private readonly authenticator: IAuthenticator, private readonly httpClient: HttpClient, - private readonly settingsProvider: ISettingsProvider + private readonly settingsProvider: ISettingsProvider, + private readonly logger: Logger ) { } /** @@ -322,6 +325,7 @@ export class UsersService { }); await this.getTokenFromResponse(response); + this.logger.trackEvent(eventTypes.trace, { message: "User successfully created with OAuth." }); } private async getTokenFromResponse(response: HttpResponse): Promise { diff --git a/src/startup.runtime.ts b/src/startup.runtime.ts index 42ff5b0f9..d3b8166c2 100644 --- a/src/startup.runtime.ts +++ b/src/startup.runtime.ts @@ -4,6 +4,8 @@ import { StyleRuntimeModule } from "@paperbits/styles/styles.runtime.module"; import { ApimRuntimeModule } from "./apim.runtime.module"; import { staticDataEnvironment } from "./../environmentConstants"; import { define } from "mime"; +import { TraceClick } from "./bindingHandlers/traceClick"; +import { Logger } from "@paperbits/common/logging"; define({ "application/x-zip-compressed": ["zip"] }, true); @@ -18,6 +20,9 @@ document.addEventListener("DOMContentLoaded", () => { } injector.resolve("autostart"); + const logger = injector.resolve("logger"); + const traceClick = new TraceClick(logger); + traceClick.setupBinding(); }); window.onbeforeunload = () => {