UNPKG

@applicaster/zapp-react-native-utils

Version:

Applicaster Zapp React Native utilities package

570 lines (437 loc) • 14.7 kB
# @applicaster/zapp-react-native-utils/analyticsUtils ![npm version](https://badge.fury.io/js/%40applicaster%2Fzapp-react-native-utils.svg) ![logo](../../../logo.png) ### Part of the [Zapp React Native Utils](../README.md) package The Analytics Utils module contains many of the helper methods we use to implement and manipulate analytic events. The module is part of Quick Brick core and its releases coincide with [releases of Quick Brick](../../../CHANGELOG.md). ## Contributing When developing Analytics Utils you should not manually publish any changes manually, instead use CI to build canary versions of Quick Brick, or RCs, before doing so be sure to have the latest changes from main branch. For information how to set up the project just clone the root level Quick Brick repo and follow instructions found in [README](../../../README.md) ### API <details> <summary>@applicaster/zapp-react-native-utils/analyticsUtils</summary> - [enum OfflineItemState](#enum OfflineItemState) - [replaceAnalyticsPropsNils](#replaceAnalyticsPropsNils) - [eventForEntry](#eventForEntry) - [eventForComponent](#eventForComponent) - [sendSelectCellEvent](#sendSelectCellEvent) - [extensionsEvents](#extensionsEvents) - [generalEvents](#generalEvents) - [sendLaunchEvent](#sendLaunchEvent) - [getAnalyticsFunctions](#getAnalyticsFunctions) - [AnalyticsProvider](#AnalyticsProvider) - [useAnalytics](#useAnalytics) ##### enum OfflineItemState ```ts export enum OfflineItemState { notExist = "NOT_EXISTS", preparing = "PREPARING", inProgress = "IN_PROGRESS", error = "ERROR", completed = "COMPLETED", expired = "EXPIRED", } ``` ##### replaceAnalyticsPropsNils ```ts export const replaceAnalyticsPropsNils = (analyticsProps) => { return Object.keys(analyticsProps).reduce((acc, key) => { if (R.isNil(analyticsProps[key])) { acc[key] = "N/A"; } else { acc[key] = analyticsProps[key]; } return acc; }, {}); }; ``` ##### eventForEntry ```ts export function eventForEntry(item, itemIndex) { const { title, content, id, data, screen_type, position } = item; const valueType = item?.type?.value; const isFreeParameter = item?.extensions && item?.extensions?.free !== true ? "Paid" : "Free"; let analyticsProps = { [ANALYTICS_ENTRY_EVENTS.ITEM_TYPE]: valueType || item?.type, [ANALYTICS_ENTRY_EVENTS.ITEM_TITLE]: title, [ANALYTICS_ENTRY_EVENTS.ITEM_ID]: id, [ANALYTICS_ENTRY_EVENTS.ITEM_INDEX]: itemIndex || position || null, }; analyticsProps = replaceAnalyticsPropsNils(analyticsProps); return analyticsProps; } ``` ##### eventForComponent ```ts export function eventForComponent(component, headerTitle) { const { id, component_type, styles, data, rules } = component; const { cell_plugin_configuration_id = null } = styles; const { connected, target, source, type } = data; let analyticsProps = { [ANALYTICS_COMPONENT_EVENTS.COMPONENT_ID]: id, [ANALYTICS_COMPONENT_EVENTS.COMPONENT_TYPE]: component_type, [ANALYTICS_COMPONENT_EVENTS.CELL_STYLE]: cell_plugin_configuration_id || ANALYTICS_CORE_EVENTS.ITEM_NOT_AVAILABLE, [ANALYTICS_COMPONENT_EVENTS.COMPONENT_SOURCE]: source, [ANALYTICS_COMPONENT_EVENTS.COMPONENT_SOURCE_TYPE]: type, [ANALYTICS_COMPONENT_EVENTS.HEADER_TITLE]: headerTitle, }; analyticsProps = replaceAnalyticsPropsNils(analyticsProps); return analyticsProps; } ``` ##### sendSelectCellEvent ```ts export function sendSelectCellEvent(item, component, headerTitle, itemIndex) { const analyticsProperties = R.compose( R.reject(R.isNil), R.mergeRight(eventForEntry(item, itemIndex + 1)) )(eventForComponent(component, headerTitle)); postAnalyticEvent(ANALYTICS_CORE_EVENTS.TAP_CELL, analyticsProperties); } ``` ##### extensionsEvents ```ts export const extensionsEvents = (extensions) => { let customProperties = extensions?.analyticsCustomProperties; if (R.isNil(customProperties)) { return {}; } const mapKeys = (obj, fn) => Object.keys(obj).reduce((acc, k) => { acc[fn(obj[k], k, obj)] = JSON.stringify(obj[k]); return acc; }, {}); return mapKeys( customProperties, (val, key) => ANALYTICS_ENTRY_EVENTS.ITEM_CUSTOM_PROPERTY + "_" + key ); }; ``` ##### generalEvents ```ts export const generalEvents = (appData) => { if (!QuickBrickCommunicationModule) { analyticsUtilsLogger.warning({ message: "failed to initialize QuickBrickCommunicationModule", }); return {}; } const { versionName, buildVersion, uuid, riversConfigurationId, appName } = QuickBrickCommunicationModule || {}; let analyticsProps = { [ANALYTICS_LAUNCH_EVENTS.VERSION_NAME]: appData?.version_name || versionName, [ANALYTICS_LAUNCH_EVENTS.BUILD_VERSION]: appData?.build_version || buildVersion, [ANALYTICS_LAUNCH_EVENTS.UUID]: uuid, [ANALYTICS_LAUNCH_EVENTS.RIVERS_CONFIGURATION_ID]: appData?.rivers_configuration_id || riversConfigurationId, [APPLICATION_EVENTS.APPLICATION_VERSION]: (appData?.version_name || versionName) + (__DEV__ ? " debug" : ""), [APPLICATION_EVENTS.APPLICATION_NAME]: `${getPlatform()} | ${ appData?.app_name || appName }`, }; analyticsProps = replaceAnalyticsPropsNils(analyticsProps); return analyticsProps; }; ``` ##### sendLaunchEvent ```ts export function sendLaunchEvent(appData) { if (!QuickBrickCommunicationModule) { analyticsUtilsLogger.warning({ message: "failed to initialize QuickBrickCommunicationModule", }); return; } postAnalyticEvent(ANALYTICS_CORE_EVENTS.LAUNCH_APP, generalEvents(appData)); } ``` ##### getAnalyticsFunctions ```ts export function getAnalyticsFunctions({ props, appData, }: GetAnalyticsFunctionsArgs): AnalyticsFunctions { const buildVersion = appData.build_version || QuickBrickCommunicationModule?.buildVersion; const versionName = appData.version_name || QuickBrickCommunicationModule?.versionName; const uuid = appData.uuid || QuickBrickCommunicationModule?.uuid || "1234"; const riversConfigurationId = appData.rivers_configuration_id || QuickBrickCommunicationModule?.riversConfigurationId; const appDataEvents = { [ANALYTICS_LAUNCH_EVENTS.VERSION_NAME]: versionName, [ANALYTICS_LAUNCH_EVENTS.BUILD_VERSION]: buildVersion, [ANALYTICS_LAUNCH_EVENTS.UUID]: uuid, [ANALYTICS_LAUNCH_EVENTS.RIVERS_CONFIGURATION_ID]: riversConfigurationId, [APPLICATION_EVENTS.APPLICATION_VERSION]: versionName + (__DEV__ ? " debug" : ""), [APPLICATION_EVENTS.APPLICATION_NAME]: `${getPlatform()} | ${ appData?.app_name }`, }; const sendOnClickEvent = (extraProps: ExtraProps) => { const componentData = props?.component || extraProps.component; const zappPipesData = props?.zappPipesData || extraProps.zappPipesData; const analyticsProps = { ...appDataEvents, ...eventForEntry(extraProps?.item, extraProps?.index), ...eventForComponent(componentData, zappPipesData?.data?.title), ...extensionsEvents(extraProps?.item?.extensions || {}), }; postAnalyticEvent(ANALYTICS_CORE_EVENTS.TAP_CELL, analyticsProps); }; const sendHeaderClickEvent = (extraProps: ExtraProps) => { const componentData = props?.component || extraProps.component; const zappPipesData = props?.zappPipesData || extraProps.zappPipesData; const analyticsProps = { ...appDataEvents, ...eventForEntry(extraProps?.item, extraProps?.index), ...eventForComponent(componentData, zappPipesData?.data?.title), ...extensionsEvents(props?.item?.extensions || {}), }; postAnalyticEvent("Tap Group Info Cell", analyticsProps); }; const sendMenuClickEvent = (extraProps: ExtraProps) => { let analyticsProps = { ...appDataEvents, ...eventForEntry(extraProps?.item, extraProps?.index), }; analyticsProps["Item Selected"] = props?.selected === extraProps?.item?.id; analyticsProps = replaceAnalyticsPropsNils(analyticsProps); postAnalyticEvent("Tap Menu Item", analyticsProps); }; const sendMenuToggleEvent = () => { let analyticsProps = appDataEvents; analyticsProps["Logo File URL"] = props?.assets?.menu_button; analyticsProps = replaceAnalyticsPropsNils(analyticsProps); postAnalyticEvent("Toolbar: Side Menu Button Clicked", analyticsProps); }; const sendBackButtonClickEvent = () => { const analyticsProps = R.set( R.lensProp("Trigger"), "nav bar" )(appDataEvents); postAnalyticEvent("Tap Navbar Back Button", analyticsProps); }; const sendHardwareBackButtonClickEvent = () => { const analyticsProps = R.set( R.lensProp("Trigger"), "device" )(appDataEvents); postAnalyticEvent("Tap Navbar Back Button", analyticsProps); }; const sendOnNavItemClickEvent = (extraProps: NavItemProps) => { const analyticsProps = { ...appDataEvents, ...eventForEntry(extraProps?.item, extraProps?.index), }; postAnalyticEvent("NavBar Button Clicked", analyticsProps); }; const sendScreenEvent = (props: ScreenDataProps) => { const screenData = (props as HookPluginProps)?.payload || props; const hookPlugin = (props as HookPluginProps)?.hookPlugin; const { home = false } = (screenData || {}) as ZappRiver; let eventName = ""; if (hookPlugin) { return; } eventName = home ? "Home screen: viewed" : `Screen viewed: ${ (screenData as ZappRiver)?.name || (screenData as ZappEntry)?.title }`; const payload = { ...appDataEvents, ...mapToScreenViewEvent(screenData as ZappRiver), }; postAnalyticEvent(eventName, payload); }; const sendDownloadsEvent = ({ item, state, }: { item: ZappEntry; state: OfflineItemState; }) => { const analyticsProperties = replaceAnalyticsPropsNils({ ...appDataEvents, id: item?.id, title: item?.title, contentType: item?.content?.type, }); const event = downloadsEventForState(state); postAnalyticEvent(event, analyticsProperties); }; return { sendOnClickEvent, sendMenuClickEvent, sendMenuToggleEvent, sendBackButtonClickEvent, sendOnNavItemClickEvent, sendHeaderClickEvent, sendHardwareBackButtonClickEvent, sendScreenEvent, sendDownloadsEvent, }; } ``` ##### AnalyticsProvider ```ts export function AnalyticsProvider(props: ComponentWithChildrenProps) { return ( <AnalyticsContext.Provider value={getAnalyticsFunctions}> {props?.children} </AnalyticsContext.Provider> ); } ``` ##### useAnalytics ```ts export function useAnalytics(props: any): any { const { appData } = usePickFromState(["appData"]); const getAnalyticsFunctions = React.useContext(AnalyticsContext); const analyticsFunctions = React.useMemo( () => getAnalyticsFunctions({ props, appData }), [props, appData] ); return analyticsFunctions; } ``` </details> <details> <summary>@applicaster/zapp-react-native-utils/analyticsUtils/analyticsMapper</summary> - [class Mapper](#class Mapper) - [Mapper.map](#Mapper.map) ##### class Mapper ```ts type Params = { [targetParamName: string]: string | Rules["ParamRule"]; // need to check }; enum Strategy { allowUnlisted = "allowUnlisted", warnUnlisted = "warnUnlisted", blockUnlisted = "blockUnlisted", } enum sourceType { default = "", // just rename the input param ctx = "ctx", // create new key and and pull value from storages using dot namespace notation } // Not sure I get this enum that takes an arg of (prefix) const ValueSourceType = (prefix: string = null) => { switch (prefix) { case "ctx": return sourceType.ctx; default: return sourceType.default; } }; interface Rules { ParamRule: { optional?: boolean; source?: string }; EventRule: { event?: string; ignore?: boolean; params?: Params; rename?: string; excludeParams?: String[]; regex?: string; matcher?: RegExp; strategy?: string; }; } type MapperConfiguration = { rules: Rules["EventRule"][]; strategy: string; }; type AnalyticsEvent = { name?: string; params: Params; }; export class Mapper { private configuration: MapperConfiguration = null; setConfiguration(config: MapperConfiguration) { // this.configuration = config; this.configuration = R.clone(config); this.configuration.rules.forEach((rule) => { rule.matcher = rule.regex && new RegExp(rule.regex, "gi"); }); } async map(event: AnalyticsEvent): Promise<AnalyticsEvent> {} private async applyEventRule( rule: Rules["EventRule"], event: AnalyticsEvent, matcher?: RegExp ): Promise<AnalyticsEvent> {} AnalyticsEvent(name: string, params: { [key: string]: any }): AnalyticsEvent { return { name, params }; } private async applyParamRule( outName: string, rule: Rules["ParamRule"], pluckedParams: Params, eventName: string ): Promise<string> {} private async resolveSource( pluckedParams: Params, source: string, required: boolean, eventName: string ): Promise<string> {} private async getFromLocalAndSession( key: string, namespace: string = null ): Promise<string> {} private async resolveContext( context: string, compositeKey: string, required: boolean, eventName: string ): Promise<string> {} private popValue( pluckedParams: Params, key: string, required: boolean, eventName: string ): string {} ``` ##### Mapper.map ```ts async map(event: AnalyticsEvent): Promise<AnalyticsEvent> { const rules = this.configuration.rules; if (isEmptyOrNil(rules)) { analyticsUtilsLogger.verbose({ message: "No analytics mapping rules found, skipping.", data: this.configuration, }); return event; } const rule = rules.find((r) => r.matcher ? r.matcher.test(event.name) : r.event === event.name ); if (rule) { return await this.applyEventRule(rule, event, rule.matcher); } switch (this.configuration.strategy) { case Strategy.allowUnlisted: return event; case Strategy.warnUnlisted: analyticsUtilsLogger.warn({ message: `Unlisted event ${event.name}`, data: event, }); return event; case Strategy.blockUnlisted: return null; default: analyticsUtilsLogger.error({ message: `Unsupported strategy was provided: ${this.configuration.strategy}`, }); return event; } } ``` </details>