ts/store/middlewares/analytics.ts
/* eslint-disable no-fallthrough */
// disabled in order to allows comments between the switch
import * as O from "fp-ts/lib/Option";
import { getType } from "typesafe-actions";
import trackCdc from "../../features/bonus/cdc/analytics/index";
import trackCgnAction from "../../features/bonus/cgn/analytics/index";
import { loadAvailableBonuses } from "../../features/bonus/common/store/actions/availableBonusesTypes";
import trackEuCovidCertificateActions from "../../features/euCovidCert/analytics/index";
import trackFciAction from "../../features/fci/analytics";
import { fciEnvironmentSelector } from "../../features/fci/store/reducers/fciEnvironment";
import { trackBPayAction } from "../../features/wallet/onboarding/bancomatPay/analytics";
import { trackCoBadgeAction } from "../../features/wallet/onboarding/cobadge/analytics";
import trackPaypalOnboarding from "../../features/wallet/onboarding/paypal/analytics/index";
import trackZendesk from "../../features/zendesk/analytics/index";
import { mixpanel } from "../../mixpanel";
import { getNetworkErrorMessage } from "../../utils/errors";
import {
analyticsAuthenticationCompleted,
analyticsAuthenticationStarted
} from "../actions/analytics";
import { applicationChangeState } from "../actions/application";
import {
idpLoginUrlChanged,
idpSelected,
loginFailure,
loginSuccess,
logoutFailure,
logoutSuccess,
sessionExpired,
sessionInformationLoadFailure,
sessionInformationLoadSuccess,
sessionInvalid
} from "../actions/authentication";
import { cieAuthenticationError } from "../actions/cie";
import { contentMunicipalityLoad } from "../actions/content";
import {
identificationCancel,
identificationFailure,
identificationForceLogout,
identificationPinReset,
identificationRequest,
identificationStart,
identificationSuccess
} from "../actions/identification";
import {
profileFirstLogin,
profileLoadFailure,
profileLoadRequest,
profileUpsert,
removeAccountMotivation
} from "../actions/profile";
import { profileEmailValidationChanged } from "../actions/profileEmailValidationChange";
import { searchMessagesEnabled } from "../actions/search";
import { Action, Dispatch, MiddlewareAPI } from "../actions/types";
import {
deleteUserDataProcessing,
upsertUserDataProcessing
} from "../actions/userDataProcessing";
import { deleteAllPaymentMethodsByFunction } from "../actions/wallet/delete";
import {
addCreditCardOutcomeCode,
paymentOutcomeCode
} from "../actions/wallet/outcomeCode";
import {
abortRunningPayment,
paymentAttiva,
paymentCheck,
paymentCompletedSuccess,
paymentDeletePayment,
paymentExecuteStart,
paymentIdPolling,
paymentInitializeState,
paymentUpdateWalletPsp,
paymentVerifica,
paymentWebViewEnd
} from "../actions/wallet/payment";
import {
fetchTransactionsFailure,
fetchTransactionsRequest,
fetchTransactionsSuccess
} from "../actions/wallet/transactions";
import {
addCreditCardWebViewEnd,
addWalletCreditCardFailure,
addWalletCreditCardInit,
addWalletCreditCardRequest,
addWalletNewCreditCardFailure,
addWalletNewCreditCardSuccess,
deleteWalletFailure,
deleteWalletRequest,
deleteWalletSuccess,
refreshPMTokenWhileAddCreditCard,
setFavouriteWalletFailure,
setFavouriteWalletRequest,
setFavouriteWalletSuccess,
updatePaymentStatus
} from "../actions/wallet/wallets";
import { buildEventProperties } from "../../utils/analytics";
import { trackServicesAction } from "../../features/services/common/analytics";
import { trackMessagesActionsPostDispatch } from "../../features/messages/analytics";
import { trackContentAction } from "./contentAnalytics";
const trackAction =
(mp: NonNullable<typeof mixpanel>) =>
// eslint-disable-next-line complexity
(action: Action): void | ReadonlyArray<null> => {
switch (action.type) {
//
// Application state actions
//
case getType(applicationChangeState):
return mp.track("APP_STATE_CHANGE", {
APPLICATION_STATE_NAME: action.payload
});
//
// Authentication actions (with properties)
//
case getType(idpSelected):
return mp.track(action.type, {
SPID_IDP_ID: action.payload.id,
SPID_IDP_NAME: action.payload.name
});
case getType(idpLoginUrlChanged):
return mp.track(action.type, {
SPID_URL: action.payload.url
});
// dispatch to mixpanel when the email is validated
case getType(profileEmailValidationChanged):
return mp.track(action.type, { isEmailValidated: action.payload });
case getType(fetchTransactionsSuccess):
return mp.track(action.type, {
count: action.payload.data.length,
total: O.getOrElse(() => -1)(action.payload.total)
});
// end pay webview Payment (payment + onboarding credit card) actions (with properties)
case getType(addCreditCardWebViewEnd):
return mp.track(action.type, {
exitType: action.payload
});
case getType(paymentOutcomeCode):
return mp.track(action.type, {
outCome: O.getOrElse(() => "")(action.payload.outcome),
paymentMethodType: action.payload.paymentMethodType
});
case getType(addCreditCardOutcomeCode):
return mp.track(action.type, {
outCome: O.getOrElse(() => "")(action.payload)
});
case getType(paymentWebViewEnd):
return mp.track(action.type, {
exitType: action.payload.reason,
paymentMethodType: action.payload.paymentMethodType
});
//
// Payment actions (with properties)
//
case getType(paymentAttiva.request):
case getType(paymentVerifica.request):
return mp.track(action.type, {
organizationFiscalCode: action.payload.rptId.organizationFiscalCode,
paymentNoticeNumber: action.payload.rptId.paymentNoticeNumber
});
case getType(paymentVerifica.success):
return mp.track(action.type, {
amount: action.payload.importoSingoloVersamento
});
case getType(paymentCompletedSuccess):
// PaymentCompletedSuccess may be generated by a completed payment or
// by a verifica operation that return a duplicated payment error.
// Only in the former case we have a transaction and an amount.
if (action.payload.kind === "COMPLETED") {
const amount = action.payload.transaction?.amount.amount;
mp.track(action.type, {
amount,
kind: action.payload.kind
});
return mp.getPeople().trackCharge(amount ?? -1, {});
} else {
return mp.track(action.type, {
kind: action.payload.kind
});
}
//
// Wallet / payment failure actions (reason in the payload)
//
case getType(addWalletCreditCardFailure):
return mp.track(action.type, {
reason: action.payload.kind,
// only GENERIC_ERROR could have details of the error
error:
action.payload.kind === "GENERIC_ERROR"
? action.payload.reason
: "n/a"
});
case getType(addWalletNewCreditCardFailure):
return mp.track(action.type);
case getType(paymentAttiva.failure):
case getType(paymentVerifica.failure):
case getType(paymentIdPolling.failure):
case getType(paymentCheck.failure):
return mp.track(action.type, {
reason: action.payload
});
case getType(updatePaymentStatus.failure):
return mp.track(action.type, {
reason: getNetworkErrorMessage(action.payload)
});
// logout / load message / delete wallets / failure
case getType(deleteAllPaymentMethodsByFunction.failure):
case getType(upsertUserDataProcessing.failure):
case getType(logoutFailure):
return mp.track(action.type, {
reason: action.payload.error.message
});
// Failures with reason as Error and optional description
case getType(cieAuthenticationError):
return mp.track(action.type, action.payload);
// Failures with reason as Error
case getType(sessionInformationLoadFailure):
case getType(profileLoadFailure):
case getType(profileUpsert.failure):
case getType(refreshPMTokenWhileAddCreditCard.failure):
case getType(deleteWalletFailure):
case getType(setFavouriteWalletFailure):
case getType(fetchTransactionsFailure):
case getType(paymentDeletePayment.failure):
case getType(paymentUpdateWalletPsp.failure):
case getType(paymentExecuteStart.failure):
// Bonus vacanze
case getType(loadAvailableBonuses.failure):
return mp.track(action.type, {
reason: action.payload.message
});
// track when a missing municipality is detected
case getType(contentMunicipalityLoad.failure):
return mp.track(action.type, {
reason: action.payload.error.message,
codice_catastale: action.payload.codiceCatastale
});
// download / delete profile
case getType(upsertUserDataProcessing.success):
return mp.track(action.type, action.payload);
// wallet
case getType(updatePaymentStatus.success):
return mp.track(action.type, {
pagoPA: action.payload.paymentMethod?.pagoPA
});
//
// Actions (without properties)
//
// authentication
case getType(loginFailure):
return mp.track(action.type, {
idp: action.payload.idp,
reason: action.payload.error.message
});
case getType(loginSuccess):
return mp.track(action.type, {
idp: action.payload.idp
});
case getType(analyticsAuthenticationStarted):
case getType(analyticsAuthenticationCompleted):
case getType(sessionInformationLoadSuccess):
case getType(sessionExpired):
case getType(sessionInvalid):
case getType(logoutSuccess):
// identification
// identificationSuccess is handled separately
// because it has a payload.
case getType(identificationRequest):
case getType(identificationStart):
case getType(identificationCancel):
case getType(identificationFailure):
case getType(identificationPinReset):
case getType(identificationForceLogout):
// profile
case getType(profileUpsert.success):
case getType(profileLoadRequest):
// messages
case getType(searchMessagesEnabled):
// wallet
case getType(addWalletCreditCardInit):
case getType(addWalletCreditCardRequest):
case getType(addWalletNewCreditCardSuccess):
case getType(deleteAllPaymentMethodsByFunction.request):
case getType(deleteAllPaymentMethodsByFunction.success):
case getType(deleteWalletRequest):
case getType(deleteWalletSuccess):
case getType(setFavouriteWalletRequest):
case getType(setFavouriteWalletSuccess):
case getType(fetchTransactionsRequest):
case getType(refreshPMTokenWhileAddCreditCard.request):
case getType(refreshPMTokenWhileAddCreditCard.success):
case getType(updatePaymentStatus.request):
// payment
case getType(abortRunningPayment):
case getType(paymentInitializeState):
case getType(paymentAttiva.success):
case getType(paymentIdPolling.request):
case getType(paymentIdPolling.success):
case getType(paymentCheck.request):
case getType(paymentCheck.success):
case getType(paymentExecuteStart.request):
case getType(paymentExecuteStart.success):
case getType(paymentUpdateWalletPsp.request):
case getType(paymentUpdateWalletPsp.success):
case getType(paymentDeletePayment.request):
case getType(paymentDeletePayment.success):
// profile First time Login
case getType(profileFirstLogin):
// other
case getType(loadAvailableBonuses.success):
case getType(loadAvailableBonuses.request):
return mp.track(action.type);
case getType(deleteUserDataProcessing.request):
return mp.track(action.type, { choice: action.payload });
case getType(removeAccountMotivation):
case getType(deleteUserDataProcessing.success):
return mp.track(action.type, action.payload);
case getType(deleteUserDataProcessing.failure):
return mp.track(action.type, {
choice: action.payload.choice,
reason: action.payload.error.message
});
// identification: identificationSuccess
case getType(identificationSuccess):
return mp.track(
action.type,
buildEventProperties("UX", "confirm", {
identification_method: action.payload.isBiometric ? "bio" : "pin"
})
);
}
};
/*
* The middleware acts as a general hook in order to track any meaningful action
*/
export const actionTracking =
(middleware: MiddlewareAPI) =>
(next: Dispatch) =>
(action: Action): Action => {
if (mixpanel !== undefined) {
// Call mixpanel tracking only after we have
// initialized mixpanel with the API token
// Be aware that, at this point, tracking is called before
// the action has been dispatched to the redux store
void trackAction(mixpanel)(action);
void trackBPayAction(mixpanel)(action);
void trackCoBadgeAction(mixpanel)(action);
void trackCgnAction(mixpanel)(action);
void trackContentAction(mixpanel)(action);
void trackServicesAction(mixpanel)(action);
void trackEuCovidCertificateActions(mixpanel)(action);
void trackPaypalOnboarding(mixpanel)(action);
void trackZendesk(mixpanel)(action);
void trackCdc(mixpanel)(action);
const fciEnvironment = fciEnvironmentSelector(middleware.getState());
void trackFciAction(mixpanel, fciEnvironment)(action);
}
// This dispatches the action towards the redux store
const result = next(action);
if (mixpanel !== undefined) {
// Call mixpanel tracking only after we have
// initialized mixpanel with the API token
// Be aware that, at this point, tracking is called after
// the action has been dispatched to the redux store
trackMessagesActionsPostDispatch(action, middleware.getState());
}
return result;
};