teamdigitale/italia-app

View on GitHub
ts/utils/analytics.ts

Summary

Maintainability
A
1 hr
Test Coverage
import { pipe } from "fp-ts/lib/function";
import * as B from "fp-ts/lib/boolean";
import { LoginUtilsError } from "@pagopa/io-react-native-login-utils";
import {
  WebViewErrorEvent,
  WebViewHttpErrorEvent
} from "react-native-webview/lib/WebViewTypes";
import URLParse from "url-parse";
import EUCOVIDCERT_ROUTES from "../features/euCovidCert/navigation/routes";
import { euCovidCertificateEnabled } from "../config";
import { mixpanelTrack } from "../mixpanel";
import { isLoginUtilsError } from "../features/lollipop/utils/login";
import {
  clearKeychainError,
  getKeychainError,
  removeKeychainError,
  setKeychainError
} from "../store/storages/keychain";

const blackListRoutes: ReadonlyArray<string> = [];

// the routes contained in this set won't be tracked in SCREEN_CHANGE_V2 event
export const noAnalyticsRoutes = new Set<string>(
  // eslint-disable-next-line sonarjs/no-empty-collection
  blackListRoutes.concat(
    euCovidCertificateEnabled ? Object.values(EUCOVIDCERT_ROUTES) : []
  )
);

export type FlowType =
  | "firstOnboarding"
  | "onBoarding"
  | "preferenze"
  | "browsing";

/**
 *
 * @param isOnBoarding existing user making new login
 * @param isFirstOnBoarding new user making new login
 * @param isUserBrowsing user navigating the app after firstOnboarding/onboarding
 * @returns
 */
export const getFlowType = (
  isOnBoarding: boolean,
  isFirstOnBoarding?: boolean,
  isUserBrowsing?: boolean
): FlowType => {
  if (isFirstOnBoarding) {
    return "firstOnboarding";
  }
  if (isOnBoarding) {
    return "onBoarding";
  }
  if (isUserBrowsing) {
    return "browsing";
  }
  return "preferenze";
};

export const booleanToYesNo = (value: boolean): "yes" | "no" =>
  pipe(
    value,
    B.fold(
      () => "no" as const,
      () => "yes" as const
    )
  );

export const numberToYesNoOnThreshold = (
  value: number,
  threshold: number = 0
) =>
  pipe(
    value > threshold,
    B.fold(
      () => "no" as const,
      () => "yes" as const
    )
  );

export const buildEventProperties = (
  eventCategory: "KO" | "TECH" | "UX",
  eventType:
    | "action"
    | "control"
    | "exit"
    | "micro_action"
    | "screen_view"
    | "confirm"
    | "error"
    | undefined,
  customProperties: Record<string, unknown> = {},
  flow?: FlowType
) => ({
  event_category: eventCategory,
  event_type: eventType,
  flow,
  ...customProperties
});

// Lollipop events
export function trackLollipopKeyGenerationSuccess(keyType?: string) {
  void mixpanelTrack("LOLLIPOP_KEY_GENERATION_SUCCESS", {
    kty: keyType
  });
}

export function trackLollipopKeyGenerationFailure(reason: string) {
  void mixpanelTrack("LOLLIPOP_KEY_GENERATION_FAILURE", {
    reason
  });
}

export function trackLollipopIdpLoginFailure(reason: string) {
  void mixpanelTrack("LOLLIPOP_IDP_LOGIN_FAILURE", {
    reason
  });
}

export function trackLollipopIsKeyStrongboxBackedSuccess(
  isStrongboxBacked: boolean
) {
  void mixpanelTrack(
    "LOLLIPOP_IS_KEY_STRONGBOX_BACKED_SUCCESS",
    buildEventProperties("TECH", undefined, {
      isStrongboxBacked
    })
  );
}

export function trackLollipopIsKeyStrongboxBackedFailure(reason: string) {
  void mixpanelTrack(
    "LOLLIPOP_IS_KEY_STRONGBOX_BACKED_FAILURE",
    buildEventProperties("KO", undefined, {
      reason
    })
  );
}

// End of lollipop events

// SPID Login
export function trackSpidLoginError(
  idpName: string | undefined,
  e: Error | LoginUtilsError | WebViewErrorEvent | WebViewHttpErrorEvent
) {
  const eventName = "SPID_ERROR";
  if (isLoginUtilsError(e)) {
    void mixpanelTrack(eventName, {
      idp: idpName,
      code: e.userInfo?.statusCode,
      description: e.userInfo?.error,
      domain: e.userInfo?.url
    });
  } else {
    const error = e as Error;
    const webViewError = e as WebViewErrorEvent;
    const webViewHttpError = e as WebViewHttpErrorEvent;
    if (webViewHttpError.nativeEvent.statusCode) {
      const { description, statusCode, url } = webViewHttpError.nativeEvent;
      void mixpanelTrack(eventName, {
        idp: idpName,
        code: statusCode,
        description,
        domain: toUrlWithoutQueryParams(url)
      });
    } else if (webViewError.nativeEvent) {
      const { code, description, domain } = webViewError.nativeEvent;
      void mixpanelTrack(eventName, {
        idp: idpName,
        code,
        description,
        domain
      });
    } else if (error.message !== undefined) {
      void mixpanelTrack(eventName, {
        idp: idpName,
        code: error.message,
        description: error.message,
        domain: error.message
      });
    }
  }
}
// End of SPID Login

// Keychain
// workaround to send keychainError for Pixel devices
// TODO: REMOVE AFTER FIXING https://pagopa.atlassian.net/jira/software/c/projects/IABT/boards/92?modal=detail&selectedIssue=IABT-1441
export function trackKeychainFailures() {
  if (getKeychainError) {
    void mixpanelTrack("KEY_CHAIN_GET_GENERIC_PASSWORD_FAILURE", {
      reason: getKeychainError,
      ...buildEventProperties("TECH", undefined)
    });
  }
  if (setKeychainError) {
    void mixpanelTrack("KEY_CHAIN_SET_GENERIC_PASSWORD_FAILURE", {
      reason: setKeychainError,
      ...buildEventProperties("TECH", undefined)
    });
  }
  if (removeKeychainError) {
    void mixpanelTrack("KEY_CHAIN_REMOVE_GENERIC_PASSWORD_FAILURE", {
      reason: removeKeychainError,
      ...buildEventProperties("TECH", undefined)
    });
  }
  clearKeychainError();
}

function toUrlWithoutQueryParams(url: string) {
  const urlAsURL = URLParse(url);
  return urlAsURL.origin + urlAsURL.pathname;
}