Skip to content

Commit

Permalink
chore: [IOPID-2137,IOPID-2479] CieID events integration (#6428)
Browse files Browse the repository at this point in the history
## Short description
This PR adds event tracking to the `CieID` login flow and edit copy on
`CieIdWizard` screen

## List of changes proposed in this pull request
- Added event tracking for the various scenarios in the new `CieID`
login flow.
- Updated some `SPID` error events with the addition of some event
`properties`
- Updated `CieIdWizard` description

## Demo
<details><summary>Test wizards</summary>
<video
src="https://github.com/user-attachments/assets/5f2469b5-5eae-45f7-8874-e5da3e61e0b7"></video>
</details>
<details><summary>Test operation cancel and login success</summary>
<video
src="https://github.com/user-attachments/assets/506e0a29-2720-46c2-b21b-aaa4e908cc4c"></video>
</details>

## Screen

<img width=300
src="https://github.com/user-attachments/assets/05edcd72-ac6c-4361-bbb4-28cabdf89153"
/>

## How to test
Run the app and navigate through the `CieID` login/wizards flows and
check if all the events are properly dispatched.

### Events table
<details>

| TAG | EVENT NAME | EVENT CATEGORY | EVENT TYPE | TRIGGER | PROPERTY 1
| PROPERTY 2 |

|:----------|:---------------------------------------|:-------------------|:-------------|:------------------------------------------------------------------|:-------------------------|:-------------------------|
|New | LOGIN_CIE_IDENTIFICATION_MODE | UX | screen view | screen view >
bottom sheet “How do you want to login?” | | |
|New | LOGIN_CIE_WIZARD_SELECTED | UX | action | tap > "We give you a
hand” | | |
|New | LOGIN_CIE_WIZARD_CIEID | UX | screen view | screen view > “Do you
have the CieID app?” | | |
|New | LOGIN_CIE_WIZARD_CIEID_SELECTED | UX | action | tap > "Yes, log
in with CieID" | | |
|New | LOGIN_CIE_WIZARD_PIN | UX | screen view | screen view > “Do you
have your Electronic Identity Card and PIN?”| | |
|New | LOGIN_CIE_WIZARD_PIN_INFO | UX | action | tap > "Where can I find
the CIE PIN?" | | |
|New | LOGIN_CIE_WIZARD_PIN_SELECTED | UX | action | tap > "Yes, enter
with CIE + PIN" | | |
|New | LOGIN_CIE_WIZARD_SPID | UX | screen view | screen view > “Do you
have SPID?” | | |
|New | LOGIN_CIE_WIZARD_SPID_SELECTED | UX | action | tap > "Yes, login
with SPID" | | |
|New | LOGIN_CIE_WIZARD_IDP_ACTIVATION | UX | screen view | screen view
> "Activate your digital identity!" | | |
|New | LOGIN_CIE_PIN_SELECTED | UX | action | tap > "Login with CIE +
PIN" | | |
|New | LOGIN_CIEID_SELECTED | UX | action | tap > "Login with CieID" |
security_level = L2 / L3 | |
|New | LOGIN_CIEID_APP_NOT_FOUND | KO | error | screen > "We can't find
the CieID app" | | |
|New | LOGIN_CIEID_APP_NOT_FOUND_DOWNLOAD | UX | action | tap > "scarica
CieID" | | |
|New | LOGIN_CIEID_UX_SUCCESS | UX | confirm | Login with CieID success
| login_session= 365 / 30 | |
|New | LOGIN_ERROR_MESSAGE | KO | error | screen view > "It was not
possible to access" | idp |error message |
|New | LOGIN_CIEID_FALLBACK_CIE_PIN | KO | | screen > "Can't log in with
CieID?" | | |
|New | LOGIN_CIEID_FALLBACK_CIE_PIN_SELECTED | UX | action | tap >
"Login with CIE + PIN" | | |
|New | LOGIN_CIEID_FALLBACK_SPID | KO | | screen > "Can't log in with
CieID?" | | |
|New | LOGIN_CIEID_FALLBACK_CIE_SPID_SELECTED | UX | action | tap >
"Login with SPID" | | |
|Updated | LOGIN_SPID_GENERIC_ERROR | KO | | event > "Generic error" |
idp | error message |
|Updated | LOGIN_SPID_ATTEMPTS_ERROR | KO | | event > "Max login
attempts reached" | idp | |
|Updated | LOGIN_SPID_SECURITY_LEVEL | KO | | event > "Security level" |
idp | |
|Updated | LOGIN_SPID_TIMEOUT_ERROR | KO | | event > "Timeout error" |
idp | |
|Updated | LOGIN_SPID_DATA_SHARING_ERROR | KO | | event > "Data sharing
consent denied" | idp | |
|Updated | LOGIN_SPID_IDENTITY_ERROR | KO | | event > "Identity
suspended or revoked" | idp | |
|Updated | LOGIN_SPID_CANCEL_ERROR | KO | | event > "login cancelled by
the user" | idp | |

</details>

---------

Co-authored-by: mariateresaventura <[email protected]>
  • Loading branch information
ChrisMattew and mariateresaventura authored Nov 22, 2024
1 parent 33a36c5 commit 4954500
Show file tree
Hide file tree
Showing 26 changed files with 449 additions and 123 deletions.
2 changes: 1 addition & 1 deletion locales/en/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -669,7 +669,7 @@ authentication:
wizards:
cie_id_wizard:
title: Do you have the CieID app?
description: If you have the CieID app on your device, enter the code you have set for the CieID app or use your face or fingerprint.
description: "If you have the CieID app on your device, enter the code you have set for the CieID app or use your face or fingerprint.\n\nTo enter the IO app without physically using the Electronic Identity Card,you must first enable Level 1 and Level 2 access in the CieID application."
actions:
primary:
label: Yes, log in with CieID
Expand Down
2 changes: 1 addition & 1 deletion locales/it/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -669,7 +669,7 @@ authentication:
wizards:
cie_id_wizard:
title: Hai l'app CieID?
description: Se hai l’app CieID sul tuo dispositivo, inserisci il codice che hai impostato per l’app CieID o usa il tuo volto o l’impronta.
description: "Se hai l’app CieID sul tuo dispositivo, inserisci il codice che hai impostato per l’app CieID o usa il tuo volto o l’impronta.\n\nPer entrare su IO senza usare fisicamente la Carta di Identità Elettronica, devi prima abilitare l’accesso con livello 1 e 2 nell’app CieID."
actions:
primary:
label: Sì, entra con CieID
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"io_session_manager_api": "https://raw.githubusercontent.com/pagopa/io-auth-n-identity-domain/[email protected]/apps/io-session-manager/api/internal.yaml",
"io_session_manager_public_api": "https://raw.githubusercontent.com/pagopa/io-auth-n-identity-domain/[email protected]/apps/io-session-manager/api/public.yaml",
"io_public_api": "https://raw.githubusercontent.com/pagopa/io-backend/v16.4.0-RELEASE/api_public.yaml",
"io_content_specs": "https://raw.githubusercontent.com/pagopa/io-services-metadata/1.0.46/definitions.yml",
"io_content_specs": "https://raw.githubusercontent.com/pagopa/io-services-metadata/1.0.47/definitions.yml",
"io_cgn_specs": "https://raw.githubusercontent.com/pagopa/io-backend/v16.4.0-RELEASE/api_cgn.yaml",
"io_cgn_merchants_specs": "https://raw.githubusercontent.com/pagopa/io-backend/v16.4.0-RELEASE/api_cgn_operator_search.yaml",
"api_fci": "https://raw.githubusercontent.com/pagopa/io-backend/v16.4.0-RELEASE/api_io_sign.yaml",
Expand Down
36 changes: 17 additions & 19 deletions ts/features/cie/__tests__/CieIdErrorScreen.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,31 @@ import { fireEvent, render } from "@testing-library/react-native";
import React from "react";
import CieIdErrorScreen from "../screens/errors/CieIdErrorScreen";
import * as useNavigateToLoginMethod from "../../../hooks/useNavigateToLoginMethod";
import ROUTES from "../../../navigation/routes";

const mockNavigateToCiePinInsertion = jest.fn();
const mockNavigateToIdpSelection = jest.fn();
const mockNavigateToCieIdScreen = jest.fn();
const mockReplace = jest.fn();
const mockNavigate = jest.fn();

jest.mock("@react-navigation/native", () => {
const actualNav = jest.requireActual("@react-navigation/native");
return {
...actualNav,
useNavigation: () => ({
replace: mockReplace
replace: mockReplace,
navigate: mockNavigate
})
};
});
jest.mock("react-redux", () => ({
...jest.requireActual("react-redux"),
useDispatch: jest.fn
}));

describe("CieIdErrorScreen where device supports NFC", () => {
afterEach(jest.clearAllMocks);
beforeEach(() => {
jest.spyOn(useNavigateToLoginMethod, "default").mockImplementation(() => ({
navigateToCiePinInsertion: mockNavigateToCiePinInsertion,
navigateToIdpSelection: mockNavigateToIdpSelection,
navigateToCieIdLoginScreen: mockNavigateToCieIdScreen,
...jest.requireActual("../../../hooks/useNavigateToLoginMethod"),
isCieSupported: true
}));
});
Expand All @@ -37,9 +39,9 @@ describe("CieIdErrorScreen where device supports NFC", () => {

fireEvent.press(primaryAction);

expect(mockNavigateToCiePinInsertion).toHaveBeenCalled();
expect(mockNavigateToIdpSelection).not.toHaveBeenCalled();
expect(mockNavigateToCieIdScreen).not.toHaveBeenCalled();
expect(mockNavigate).toHaveBeenCalledWith(ROUTES.AUTHENTICATION, {
screen: ROUTES.CIE_PIN_SCREEN
});
expect(mockReplace).not.toHaveBeenCalled();
});
it("Should properly call replace", testReplace);
Expand All @@ -48,9 +50,7 @@ describe("CieIdErrorScreen where device doesn't support NFC", () => {
afterEach(jest.clearAllMocks);
beforeEach(() => {
jest.spyOn(useNavigateToLoginMethod, "default").mockImplementation(() => ({
navigateToCiePinInsertion: mockNavigateToCiePinInsertion,
navigateToIdpSelection: mockNavigateToIdpSelection,
navigateToCieIdLoginScreen: mockNavigateToCieIdScreen,
...jest.requireActual("../../../hooks/useNavigateToLoginMethod"),
isCieSupported: false
}));
});
Expand All @@ -63,9 +63,9 @@ describe("CieIdErrorScreen where device doesn't support NFC", () => {

fireEvent.press(primaryAction);

expect(mockNavigateToIdpSelection).toHaveBeenCalled();
expect(mockNavigateToCiePinInsertion).not.toHaveBeenCalled();
expect(mockNavigateToCieIdScreen).not.toHaveBeenCalled();
expect(mockNavigate).toHaveBeenCalledWith(ROUTES.AUTHENTICATION, {
screen: ROUTES.AUTHENTICATION_IDP_SELECTION
});
expect(mockReplace).not.toHaveBeenCalled();
});
it("Should properly call pop-to-top", testReplace);
Expand All @@ -89,8 +89,6 @@ function testReplace() {

fireEvent.press(primaryAction);

expect(mockNavigateToCiePinInsertion).not.toHaveBeenCalled();
expect(mockNavigateToIdpSelection).not.toHaveBeenCalled();
expect(mockNavigateToCieIdScreen).not.toHaveBeenCalled();
expect(mockNavigate).not.toHaveBeenCalled();
expect(mockReplace).toHaveBeenCalled();
}
2 changes: 2 additions & 0 deletions ts/features/cie/__tests__/__snapshots__/wizards.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ exports[`CieIdWizard Should match the snapshot 1`] = `
}
>
If you have the CieID app on your device, enter the code you have set for the CieID app or use your face or fingerprint.
To enter the IO app without physically using the Electronic Identity Card,you must first enable Level 1 and Level 2 access in the CieID application.
</Text>
</View>
<View
Expand Down
5 changes: 4 additions & 1 deletion ts/features/cie/__tests__/wizards.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ jest.mock("react-redux", () => ({
...jest.requireActual("react-redux"),
useDispatch: jest.fn,
useSelector: jest.fn,
useStore: jest.fn
useStore: () => ({
getState: jest.fn()
})
}));
jest.mock("react-native-safe-area-context", () => ({
useSafeAreaInsets: jest.fn
Expand All @@ -51,6 +53,7 @@ jest.mock("../../../hooks/useNavigateToLoginMethod", () => ({
jest.mock("@gorhom/bottom-sheet", () =>
jest.requireActual("../../../__mocks__/@gorhom/bottom-sheet.ts")
);
jest.mock("../analytics");

describe(CieIdWizard, () => {
afterEach(jest.clearAllMocks);
Expand Down
119 changes: 119 additions & 0 deletions ts/features/cie/analytics/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { IdpCIE, IdpCIE_ID } from "../../../hooks/useNavigateToLoginMethod";
import { mixpanelTrack } from "../../../mixpanel";
import { updateMixpanelProfileProperties } from "../../../mixpanelConfig/profileProperties";
import { GlobalState } from "../../../store/reducers/types";
import { buildEventProperties } from "../../../utils/analytics";
import { SpidLevel } from "../../cieLogin/utils";

const SECURITY_LEVEL_MAP: Record<SpidLevel, "L2" | "L3"> = {
SpidL2: "L2",
SpidL3: "L3"
};

// Wizards screen view events
export const trackCieIdWizardScreen = async () => {
void mixpanelTrack(
"LOGIN_CIE_WIZARD_CIEID",
buildEventProperties("UX", "screen_view")
);
};
export const trackCiePinWizardScreen = async () => {
void mixpanelTrack(
"LOGIN_CIE_WIZARD_PIN",
buildEventProperties("UX", "screen_view")
);
};
export const trackSpidWizardScreen = async () => {
void mixpanelTrack(
"LOGIN_CIE_WIZARD_SPID",
buildEventProperties("UX", "screen_view")
);
};
export const trackIdpActivationWizardScreen = async () => {
void mixpanelTrack(
"LOGIN_CIE_WIZARD_IDP_ACTIVATION",
buildEventProperties("UX", "screen_view")
);
};

// Wizards action events
export const trackWizardCieIdSelected = async (
state: GlobalState,
spidLevel: SpidLevel
) => {
await updateMixpanelProfileProperties(state, {
property: "LOGIN_METHOD",
value: IdpCIE_ID.id
});
void mixpanelTrack(
"LOGIN_CIE_WIZARD_CIEID_SELECTED",
buildEventProperties("UX", "action", {
security_level: SECURITY_LEVEL_MAP[spidLevel]
})
);
};
export const trackWizardCiePinSelected = async (state: GlobalState) => {
await updateMixpanelProfileProperties(state, {
property: "LOGIN_METHOD",
value: IdpCIE.id
});
void mixpanelTrack(
"LOGIN_CIE_WIZARD_PIN_SELECTED",
buildEventProperties("UX", "action")
);
};
export const trackWizardCiePinInfoSelected = async () => {
void mixpanelTrack(
"LOGIN_CIE_WIZARD_PIN_INFO",
buildEventProperties("UX", "action")
);
};
export const trackWizardSpidSelected = async () => {
void mixpanelTrack(
"LOGIN_CIE_WIZARD_SPID_SELECTED",
buildEventProperties("UX", "action")
);
};

// Cie id not installed screen view events
export const trackCieIdNotInstalledScreen = async () => {
void mixpanelTrack(
"LOGIN_CIEID_APP_NOT_FOUND",
buildEventProperties("KO", "error")
);
};
// Cie id not installed action events
export const trackCieIdNotInstalledDownloadAction = async () => {
void mixpanelTrack(
"LOGIN_CIEID_APP_NOT_FOUND_DOWNLOAD",
buildEventProperties("UX", "action")
);
};

// Cie Id error screen view events
export const trackCieIdErrorCiePinFallbackScreen = async () => {
void mixpanelTrack(
"LOGIN_CIEID_FALLBACK_CIE_PIN",
buildEventProperties("KO", undefined)
);
};
export const trackCieIdErrorSpidFallbackScreen = async () => {
void mixpanelTrack(
"LOGIN_CIEID_FALLBACK_SPID",
buildEventProperties("KO", undefined)
);
};

// Cie Id error screen action events
export const trackCieIdErrorCiePinSelected = async () => {
void mixpanelTrack(
"LOGIN_CIEID_FALLBACK_CIE_PIN_SELECTED",
buildEventProperties("UX", "action")
);
};
export const trackCieIdErrorSpidSelected = async () => {
void mixpanelTrack(
"LOGIN_CIEID_FALLBACK_CIE_SPID_SELECTED",
buildEventProperties("UX", "action")
);
};
2 changes: 2 additions & 0 deletions ts/features/cie/components/CieIdNotInstalled.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Linking, Platform } from "react-native";
import { OperationResultScreenContent } from "../../../components/screens/OperationResultScreenContent";
import { useIONavigation } from "../../../navigation/params/AppParamsList";
import I18n from "../../../i18n";
import { trackCieIdNotInstalledDownloadAction } from "../analytics";

export const CIE_ID_IOS_LINK =
"https://apps.apple.com/it/app/cieid/id1504644677";
Expand All @@ -28,6 +29,7 @@ const CieIdNotInstalled = ({ isUat }: CieIdNotInstalledProps) => {
"authentication.cie_id.cie_not_installed.primary_action_label"
),
onPress: () => {
void trackCieIdNotInstalledDownloadAction();
void Linking.openURL(
Platform.select({
ios: CIE_ID_IOS_LINK,
Expand Down
7 changes: 6 additions & 1 deletion ts/features/cie/screens/CieIdNotInstalledScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import React from "react";
import React, { useEffect } from "react";
import { RouteProp, useRoute } from "@react-navigation/native";
import CieIdNotInstalled from "../components/CieIdNotInstalled";
import { AuthenticationParamsList } from "../../../navigation/params/AuthenticationParamsList";
import ROUTES from "../../../navigation/routes";
import { trackCieIdNotInstalledScreen } from "../analytics";

const CieIdNotInstalledScreen = () => {
const { params } =
useRoute<
RouteProp<AuthenticationParamsList, typeof ROUTES.CIE_NOT_INSTALLED>
>();

useEffect(() => {
void trackCieIdNotInstalledScreen();
}, []);

return <CieIdNotInstalled {...params} />;
};

Expand Down
49 changes: 41 additions & 8 deletions ts/features/cie/screens/errors/CieIdErrorScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
import React from "react";
import React, { useEffect } from "react";
import { OperationResultScreenContent } from "../../../../components/screens/OperationResultScreenContent";
import useNavigateToLoginMethod from "../../../../hooks/useNavigateToLoginMethod";
import useNavigateToLoginMethod, {
IdpCIE
} from "../../../../hooks/useNavigateToLoginMethod";
import { useIONavigation } from "../../../../navigation/params/AppParamsList";
import I18n from "../../../../i18n";
import { TranslationKeys } from "../../../../../locales/locales";
import { useAvoidHardwareBackButton } from "../../../../utils/useAvoidHardwareBackButton";
import ROUTES from "../../../../navigation/routes";
import {
trackCieIdErrorCiePinFallbackScreen,
trackCieIdErrorCiePinSelected,
trackCieIdErrorSpidFallbackScreen,
trackCieIdErrorSpidSelected
} from "../../analytics";
import { useIODispatch } from "../../../../store/hooks";
import { idpSelected } from "../../../../store/actions/authentication";

const CIE_PIN_DESC: TranslationKeys =
"authentication.cie_id.error_screen.cie_pin_supported.description";
Expand All @@ -17,12 +27,20 @@ const SPID_ACTION_LABEL: TranslationKeys =
"authentication.cie_id.error_screen.cie_pin_not_supported.primary_action_label";

const CieIdErrorScreen = () => {
const { navigateToIdpSelection, navigateToCiePinInsertion, isCieSupported } =
useNavigateToLoginMethod();
const { replace } = useIONavigation();
const { isCieSupported } = useNavigateToLoginMethod();
const dispatch = useIODispatch();
const { replace, navigate } = useIONavigation();

useAvoidHardwareBackButton();

useEffect(() => {
if (isCieSupported) {
void trackCieIdErrorCiePinFallbackScreen();
} else {
void trackCieIdErrorSpidFallbackScreen();
}
}, [isCieSupported]);

const subtitle = I18n.t(isCieSupported ? CIE_PIN_DESC : SPID_DESC);
const primaryActionLabel = I18n.t(
isCieSupported ? CIE_PIN_ACTION_LABEL : SPID_ACTION_LABEL
Expand All @@ -43,9 +61,24 @@ const CieIdErrorScreen = () => {
testID: "cie-id-error-primary-action",
label: primaryActionLabel,
accessibilityLabel: primaryActionLabel,
onPress: isCieSupported
? navigateToCiePinInsertion
: navigateToIdpSelection
onPress: () => {
if (isCieSupported) {
void trackCieIdErrorCiePinSelected();
// Since this screen will only be accessible after the user has already
// made their choice on the Opt-In screen, we can navigate directly to it
dispatch(idpSelected(IdpCIE));
navigate(ROUTES.AUTHENTICATION, {
screen: ROUTES.CIE_PIN_SCREEN
});
} else {
void trackCieIdErrorSpidSelected();
// Since this screen will only be accessible after the user has already
// made their choice on the Opt-In screen, we can navigate directly to it
navigate(ROUTES.AUTHENTICATION, {
screen: ROUTES.AUTHENTICATION_IDP_SELECTION
});
}
}
}}
secondaryAction={{
testID: "cie-id-error-secondary-action",
Expand Down
Loading

0 comments on commit 4954500

Please sign in to comment.