Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,16 @@ import { pipe } from "fp-ts/lib/function";
import { GlobalState } from "../../../../store/reducers/types";
import { itwAuthLevelSelector } from "../../common/store/selectors/preferences";
import { getCredentialStatus } from "../../common/utils/itwCredentialStatusUtils";
import { isItwCredential } from "../../common/utils/itwCredentialUtils";
import { CredentialType } from "../../common/utils/itwMocksUtils";
import {
itwCredentialsEidStatusSelector,
itwCredentialsSelector
} from "../../credentials/store/selectors";
import { itwLifecycleIsITWalletValidSelector } from "../../lifecycle/store/selectors";
import { ItwJwtCredentialStatus } from "../../common/utils/itwTypesUtils";
import { mapPIDStatusToMixpanel } from "../utils";
import {
ItwStatus,
ItwPIDStatus,
ItwCredentialMixpanelStatus,
CREDENTIAL_STATUS_MAP
CREDENTIAL_STATUS_MAP,
ItwCredentialMixpanelStatus
} from "../utils/types";
import { ItwBaseProperties } from "./propertyTypes";

Expand All @@ -27,9 +23,10 @@ export const buildItwBaseProperties = (
state: GlobalState
): ItwBaseProperties => {
const isItwL3 = itwLifecycleIsITWalletValidSelector(state);
const pidStatus = itwCredentialsEidStatusSelector(state);

const ITW_STATUS_V2 = getWalletStatus(state);
const ITW_PID = getPIDMixpanelStatus(state, true);
Copy link
Collaborator

@RiccardoMolinari95 RiccardoMolinari95 Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When only eID is available, ITW_ID_V2 should be mapped, but ITW_PID must stay not_available. After upgrade, V2 props must stop being sent to preserve their last value. getPIDMixpanelStatus was used for that.

Image Image

Copy link
Contributor Author

@mastro993 mastro993 Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated the functions. Could you check and verify if they are ok?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now it works correctly! 💪🏻

const ITW_STATUS_V2 = itwAuthLevelSelector(state) ?? "not_active";
const ITW_PID = mapPIDStatusToMixpanel(pidStatus);

const ITW_PG_V3 = getMixpanelCredentialStatus(
CredentialType.DRIVING_LICENSE,
Expand All @@ -53,7 +50,7 @@ export const buildItwBaseProperties = (
*/
const v2Props = !isItwL3
? {
ITW_ID_V2: getPIDMixpanelStatus(state, false),
ITW_ID_V2: mapPIDStatusToMixpanel(pidStatus),
ITW_PG_V2: getMixpanelCredentialStatus(
CredentialType.DRIVING_LICENSE,
state
Expand All @@ -79,32 +76,6 @@ export const buildItwBaseProperties = (
};
};

const getWalletStatus = (state: GlobalState): ItwStatus => {
const authLevel = itwAuthLevelSelector(state);
return authLevel ? authLevel : "not_active";
};

/**
* Returns the PID status for Mixpanel analytics.
* - If `isL3` is true → we consider the status from the current L3 PID (IT Wallet).
* - If `isL3` is false → we use the current eID status.
*/
export const getPIDMixpanelStatus = (
state: GlobalState,
isL3: boolean
): ItwPIDStatus =>
pipe(
isL3
? pipe(
itwLifecycleIsITWalletValidSelector(state),
O.fromPredicate(Boolean),
O.chain(() => O.fromNullable(itwCredentialsEidStatusSelector(state)))
)
: O.fromNullable(itwCredentialsEidStatusSelector(state)),
O.map<ItwJwtCredentialStatus, ItwPIDStatus>(mapPIDStatusToMixpanel),
O.getOrElse((): ItwPIDStatus => "not_available")
);

/**
* Returns the Mixpanel status for a credential type, considering IT Wallet.
* - If `isItwL3` is explicitly false, returns `"not_available"`.
Expand All @@ -121,9 +92,6 @@ const getMixpanelCredentialStatus = (
return "not_available";
}
const credential = itwCredentialsSelector(state)[type];
if (isItwL3 && credential && !isItwCredential(credential)) {
return "not_available";
}

return pipe(
O.fromNullable(credential),
Expand Down
17 changes: 8 additions & 9 deletions ts/features/itwallet/analytics/properties/propertyUpdaters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,13 @@ import {
isMixpanelInstanceInitialized,
registerSuperProperties
} from "../../../../mixpanel";
import { isItwAnalyticsCredential } from "../utils";
import { isItwAnalyticsCredential, mapPIDStatusToMixpanel } from "../utils";
import { itwCredentialsEidStatusSelector } from "../../credentials/store/selectors";
import {
ItwProfileProperties,
forceUpdateItwProfileProperties
} from "./profileProperties";
import {
buildItwBaseProperties,
getPIDMixpanelStatus
} from "./basePropertyBuilder";
import { buildItwBaseProperties } from "./basePropertyBuilder";
import {
ItwSuperProperties,
buildItwSuperProperties,
Expand Down Expand Up @@ -66,15 +64,16 @@ export const updateItwStatusAndPIDProperties = (state: GlobalState) => {
return;
}

const isItwL3 = itwLifecycleIsITWalletValidSelector(state);
const eIDStatus = !isItwL3 ? getPIDMixpanelStatus(state, false) : undefined;
const pidStatus = getPIDMixpanelStatus(state, true);
const pidStatus = itwCredentialsEidStatusSelector(state);

const baseProps = {
ITW_STATUS_V2: authLevel,
ITW_PID: pidStatus
ITW_PID: mapPIDStatusToMixpanel(pidStatus)
};

const isItwL3 = itwLifecycleIsITWalletValidSelector(state);
const eIDStatus = !isItwL3 ? mapPIDStatusToMixpanel(pidStatus) : undefined;

forceUpdateItwProfileProperties({
...baseProps,
...(eIDStatus ? { ITW_ID_V2: eIDStatus } : {})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ const mockedEid: StoredCredential = {
jwt: {
issuedAt: "2024-09-30T07:32:49.000Z",
expiration: jwtExpiration
}
},
spec_version: "1.0.0"
};
const mockedMdl: StoredCredential = {
credential: "",
Expand All @@ -55,7 +56,8 @@ const mockedMdl: StoredCredential = {
jwt: {
issuedAt: "2024-09-30T07:32:49.000Z",
expiration: jwtExpiration
}
},
spec_version: "1.0.0"
};

const store: DeepPartial<GlobalState> = {
Expand Down
2 changes: 1 addition & 1 deletion ts/features/itwallet/analytics/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { ItwPIDStatus, MixPanelCredential, CREDENTIALS_MAP } from "./types";
* Maps an PID status to its corresponding Mixpanel tracking status.
*/
export const mapPIDStatusToMixpanel = (
status: ItwJwtCredentialStatus
status: ItwJwtCredentialStatus | undefined
): ItwPIDStatus => {
switch (status) {
case "valid":
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { render } from "@testing-library/react-native";
import { Provider } from "react-redux";
import configureMockStore from "redux-mock-store";
import { createStore } from "redux";
import ROUTES from "../../../../../../navigation/routes";
import { applicationChangeState } from "../../../../../../store/actions/application";
import { appReducer } from "../../../../../../store/reducers";
import { GlobalState } from "../../../../../../store/reducers/types";
import { renderScreenWithNavigationStoreContext } from "../../../../../../utils/testWrapper";
import * as credentials from "../../../../credentials/store/selectors";
import * as lifecycleSelectors from "../../../../lifecycle/store/selectors";
import * as credentialUtils from "../../../utils/itwCredentialUtils";
import {
ItwCredentialStatus,
ItwJwtCredentialStatus
} from "../../../utils/itwTypesUtils";
import { ItwCredentialCard } from "../ItwCredentialCard";
import * as lifecycleSelectors from "../../../../lifecycle/store/selectors";
import * as credentials from "../../../../credentials/store/selectors";

describe("ItwCredentialCard", () => {
it.each([
Expand All @@ -21,126 +21,83 @@ describe("ItwCredentialCard", () => {
"education_enrollment",
"residency"
])("should match snapshot when credential type is %p", type => {
const globalState = appReducer(undefined, applicationChangeState("active"));

const mockStore = configureMockStore<GlobalState>();
const store: ReturnType<typeof mockStore> = mockStore({
...globalState
} as GlobalState);
jest
.spyOn(lifecycleSelectors, "itwLifecycleIsITWalletValidSelector")
.mockReturnValue(false);
jest
.spyOn(credentialUtils, "isCredentialIssuedBeforePid")
.mockReturnValue(false);

const component = render(
<Provider store={store}>
<ItwCredentialCard credentialType={type} />
</Provider>
);
const component = renderComponent({
credentialType: type
});

expect(component).toMatchSnapshot();
});

it.each([
"valid",
"expired",
"expiring",
"pending",
"unknown"
] as ReadonlyArray<ItwCredentialStatus>)(
"should match snapshot when status is %p",
status => {
const globalState = appReducer(
undefined,
applicationChangeState("active")
);

const mockStore = configureMockStore<GlobalState>();
const store: ReturnType<typeof mockStore> = mockStore({
...globalState
} as GlobalState);

const component = render(
<Provider store={store}>
<ItwCredentialCard credentialType={"mDL"} credentialStatus={status} />
</Provider>
);
expect(component).toMatchSnapshot();
}
);

it("should match snapshot when credential is pending upgrade", () => {
const globalState = appReducer(undefined, applicationChangeState("active"));

const mockStore = configureMockStore<GlobalState>();
const store: ReturnType<typeof mockStore> = mockStore({
...globalState
} as GlobalState);

jest
.spyOn(lifecycleSelectors, "itwLifecycleIsITWalletValidSelector")
.mockReturnValue(true);
jest
.spyOn(credentialUtils, "isCredentialIssuedBeforePid")
.mockReturnValue(true);

const component = renderComponent({
credentialType: "mDL"
});

const component = render(
<Provider store={store}>
<ItwCredentialCard credentialType="mDL" />
</Provider>
);
expect(component).toMatchSnapshot();
});

it.each([
describe.each([
"valid",
"jwtExpired",
"jwtExpiring"
] as ReadonlyArray<ItwJwtCredentialStatus>)(
"should match snapshot when eID is expired and credential is %p (credential status overridden to 'valid')",
credentialStatus => {
jest
.spyOn(credentials, "itwCredentialsEidStatusSelector")
.mockReturnValue("jwtExpired");

const mockStore = configureMockStore<GlobalState>();
const store = mockStore(
appReducer(undefined, applicationChangeState("active")) as GlobalState
);

const component = render(
<Provider store={store}>
<ItwCredentialCard
credentialType="mDL"
credentialStatus={credentialStatus}
/>
</Provider>
"should match snapshot when PID status is '%p'",
pidStatus => {
it.each([
"jwtExpired",
"jwtExpiring",
"expired",
"expiring",
"valid",
"invalid",
"unknown"
] as ReadonlyArray<ItwCredentialStatus>)(
"and credential status is '%p'",
credentialStatus => {
jest
.spyOn(credentialUtils, "isCredentialIssuedBeforePid")
.mockReturnValue(false);
jest
.spyOn(credentials, "itwCredentialsEidStatusSelector")
.mockReturnValue(pidStatus);
jest
.spyOn(lifecycleSelectors, "itwLifecycleIsITWalletValidSelector")
.mockReturnValue(false);

const component = renderComponent({
credentialType: "mDL",
credentialStatus
});

expect(component).toMatchSnapshot();
}
);

expect(component).toMatchSnapshot();
}
);
});

it.each([
"expired",
"expiring",
"invalid",
"unknown"
] as ReadonlyArray<ItwCredentialStatus>)(
"should match snapshot when eID is expired and credential is %p (credential status not overridden)",
// eslint-disable-next-line sonarjs/no-identical-functions
credentialStatus => {
jest
.spyOn(credentials, "itwCredentialsEidStatusSelector")
.mockReturnValue("jwtExpired");

const mockStore = configureMockStore<GlobalState>();
const store = mockStore(
appReducer(undefined, applicationChangeState("active")) as GlobalState
);

const component = render(
<Provider store={store}>
<ItwCredentialCard
credentialType="mDL"
credentialStatus={credentialStatus}
/>
</Provider>
);
const renderComponent = (props: ItwCredentialCard) => {
const initialState = appReducer(undefined, applicationChangeState("active"));
const store = createStore(appReducer, initialState as any);

expect(component).toMatchSnapshot();
}
return renderScreenWithNavigationStoreContext(
() => <ItwCredentialCard {...props} />,
ROUTES.WALLET_HOME,
{},
store
);
});
};
Loading
Loading