NTP-6184 | Clickstream events added for app update (#973)

This commit is contained in:
Mantri Ramkishor
2024-10-09 19:50:06 +05:30
committed by GitHub
5 changed files with 157 additions and 24 deletions

View File

@@ -1,6 +1,8 @@
import { BuildFlavours } from '@common/Constants';
import { BuildFlavours, CLICKSTREAM_EVENT_NAMES } from '@common/Constants';
import { ApiKeys, getApiUrl } from '@components/utlis/apiHelper';
import { getBuildVersion } from '@components/utlis/commonFunctions';
import { logError } from '@components/utlis/errorUtils';
import { addClickstreamEvent } from '@services/clickstreamEventService';
import axios from 'axios';
import { Linking, NativeModules } from 'react-native';
import RNFetchBlob from 'react-native-blob-util';
@@ -24,17 +26,33 @@ export const deleteCachedApkFiles = async () => {
}
};
export const downloadApkFromS3 = async (s3Url: string, fileName: string) => {
export const downloadApkFromS3 = async (s3Url: string, fileName: string, newAppVersion: number) => {
deleteCachedApkFiles();
const dirs = RNFetchBlob.fs.dirs;
const pathToSaveAPK = `${dirs.CacheDir}/latest-app/${fileName}.apk`;
const oldAppVersion = getBuildVersion();
try {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_APK_UPDATE_DOWNLOAD_STARTED, {
downloadPath: pathToSaveAPK,
oldAppVersion,
newAppVersion,
});
const res = await RNFetchBlob.config({
path: pathToSaveAPK,
fileCache: true,
}).fetch('GET', s3Url, {});
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_APK_UPDATE_DOWNLOAD_SUCCESS, {
downloadPath: res.path(),
oldAppVersion,
newAppVersion,
});
return res.path();
} catch (err) {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_APK_UPDATE_DOWNLOAD_FAILED, {
errorMessage: (err as Error)?.message,
oldAppVersion,
newAppVersion,
});
logError(err as Error, 'Error while downloading the latest app');
}
};
@@ -56,18 +74,25 @@ const openApkDownloadLink = (url: string) => {
export const openFallbackLonghornLink = (fallbackUrl?: string) => {
if (fallbackUrl) {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_APK_UPDATE_FALLBACK_TRIGGERED, {
fallbackUrl,
});
openApkDownloadLink(fallbackUrl);
}
};
export const downloadLatestApkAndGetFilePath = async (buildFlavour: BuildFlavours) => {
export const downloadLatestApkAndGetFilePath = async (
buildFlavour: BuildFlavours,
appVersion: number
) => {
const appUrl = await downloadLatestAppS3Url(buildFlavour);
if (!appUrl) {
return '';
}
const appFileUrl = await downloadApkFromS3(
appUrl,
`Cosmos_${BuildFlavours.FIELD_AGENTS}_${Date.now()}`
`Cosmos_${BuildFlavours.FIELD_AGENTS}_${Date.now()}`,
appVersion
);
if (!appFileUrl) {
return '';

View File

@@ -26,6 +26,8 @@ import {
openFallbackLonghornLink,
} from '@actions/appDownloadAction';
import AppUpdate from './AppUpdate';
import ReactNativeBlobUtil from 'react-native-blob-util';
import { setShouldUpdate } from '@reducers/appUpdateSlice';
interface IBlockerScreen {
children?: ReactNode;
@@ -42,15 +44,11 @@ const BlockerScreen = (props: IBlockerScreen) => {
const approvalStatus = useAppSelector((state) => state.profile?.approvalStatus);
const isLoading = useAppSelector((state) => state.profile?.isLoading);
const roles = useAppSelector((state) => state.user?.agentRoles);
const shouldUpdate = useAppSelector((state) => state.appUpdate.shouldUpdate) || {};
const isFieldAgent =
(roles?.length === 1 && roles.includes(IUserRole.ROLE_FIELD_AGENT)) ||
roles.includes(IUserRole.ROLE_OMA);
const [shouldUpdate, setShouldUpdate] = useState({
newApkCachedUrl: '',
switchToFallback: false,
});
const [showActionBtnLoader, setShowActionBtnLoader] = useState(false);
const dispatch = useAppDispatch();
@@ -61,34 +59,71 @@ const BlockerScreen = (props: IBlockerScreen) => {
let apkFileUrl;
if (GLOBAL.BUILD_FLAVOUR.includes(BuildFlavours.FIELD_AGENTS)) {
// Download app for Field agent
apkFileUrl = await downloadLatestApkAndGetFilePath(BuildFlavours.FIELD_AGENTS);
apkFileUrl = await downloadLatestApkAndGetFilePath(
BuildFlavours.FIELD_AGENTS,
appState?.fieldAgents?.version
);
} else {
// Download app for Calling agent
apkFileUrl = await downloadLatestApkAndGetFilePath(BuildFlavours.CALLING_AGENTS);
apkFileUrl = await downloadLatestApkAndGetFilePath(
BuildFlavours.CALLING_AGENTS,
appState?.telecallingAgents?.version
);
}
if (apkFileUrl) {
setShouldUpdate({ newApkCachedUrl: apkFileUrl, switchToFallback: false });
dispatch(setShouldUpdate({ newApkCachedUrl: apkFileUrl, switchToFallback: false }));
} else {
setShouldUpdate({ newApkCachedUrl: '', switchToFallback: true });
dispatch(setShouldUpdate({ newApkCachedUrl: '', switchToFallback: true }));
}
};
const handleAppUpdate = () => {
let fallbackLonghornUrl;
let oldAppVersion = getBuildVersion();
let newAppVersion = appState?.fieldAgents?.version;
if (GLOBAL.BUILD_FLAVOUR.includes('fieldAgents')) {
fallbackLonghornUrl = appState?.fieldAgents?.currentProdAPK;
} else {
newAppVersion = appState?.telecallingAgents?.version;
fallbackLonghornUrl = appState?.telecallingAgents?.currentProdAPK;
}
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_APK_UPDATE_BUTTON_CLICKED, {
apkPath: shouldUpdate.newApkCachedUrl,
oldAppVersion,
newAppVersion,
});
if (!shouldUpdate.newApkCachedUrl) {
openFallbackLonghornLink(fallbackLonghornUrl);
return;
}
installApk(shouldUpdate.newApkCachedUrl, (error) => {
if (!error) {
return;
ReactNativeBlobUtil.fs.stat(shouldUpdate.newApkCachedUrl).then((res) => {
if (res.size < 10e6) {
// Temporary check: if size less than 10MB, then file is corrupted. Go to fallback link
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_APK_UPDATE_CORRUPTED_FILE_DOWNLOADED, {
apkPath: shouldUpdate.newApkCachedUrl,
oldAppVersion,
newAppVersion,
});
openFallbackLonghornLink(fallbackLonghornUrl);
} else {
installApk(shouldUpdate.newApkCachedUrl, (error) => {
if (!error) {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_APK_UPDATE_INSTALL_STARTED, {
apkPath: shouldUpdate.newApkCachedUrl,
oldAppVersion,
newAppVersion,
});
return;
}
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_APK_UPDATE_INSTALL_FAILED, {
apkPath: shouldUpdate.newApkCachedUrl,
errorMessage: error,
oldAppVersion,
newAppVersion,
});
openFallbackLonghornLink(fallbackLonghornUrl);
});
}
openFallbackLonghornLink(fallbackLonghornUrl);
});
};
@@ -105,18 +140,22 @@ const BlockerScreen = (props: IBlockerScreen) => {
if (!flavorToUpdate) return;
const currentBuildNumber = getBuildVersion();
if (
currentBuildNumber &&
!isNaN(currentBuildNumber) &&
currentBuildNumber < flavorToUpdate.version
) {
downloadLatestApp();
} else {
setShouldUpdate({
newApkCachedUrl: '',
switchToFallback: false,
} else if (shouldUpdate.newApkCachedUrl) {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_APK_UPDATE_INSTALL_SUCCESS, {
appVersion: currentBuildNumber,
});
dispatch(
setShouldUpdate({
newApkCachedUrl: '',
switchToFallback: false,
})
);
deleteCachedApkFiles();
}
}, [appState]);

View File

@@ -1313,11 +1313,48 @@ export const CLICKSTREAM_EVENT_NAMES = {
name: 'FA_UNSYNC_FEEDBACK_CAPTURED',
description: 'Unsync feedback captured'
},
FA_API_FAILED: {
name: 'FA_API_FAILED',
description: 'API failed'
},
// Apk Update
FA_APK_UPDATE_DOWNLOAD_STARTED: {
name: 'FA_APK_UPDATE_DOWNLOAD_STARTED',
description: 'APK update download started'
},
FA_APK_UPDATE_DOWNLOAD_SUCCESS: {
name: 'FA_APK_UPDATE_DOWNLOAD_SUCCESS',
description: 'APK update download completed'
},
FA_APK_UPDATE_DOWNLOAD_FAILED: {
name: 'FA_APK_UPDATE_DOWNLOAD_FAILED',
description: 'APK update download failed'
},
FA_APK_UPDATE_BUTTON_CLICKED: {
name: 'FA_APK_UPDATE_BUTTON_CLICKED',
description: 'APK update button clicked'
},
FA_APK_UPDATE_INSTALL_STARTED: {
name: 'FA_APK_UPDATE_INSTALL_STARTED',
description: 'APK update installation started'
},
FA_APK_UPDATE_INSTALL_FAILED: {
name: 'FA_APK_UPDATE_INSTALL_FAILED',
description: 'APK update installation failed'
},
FA_APK_UPDATE_FALLBACK_TRIGGERED: {
name: 'FA_APK_UPDATE_FALLBACK_TRIGGERED',
description: 'APK update fallback triggered'
},
FA_APK_UPDATE_CORRUPTED_FILE_DOWNLOADED: {
name: 'FA_APK_UPDATE_CORRUPTED_FILE_DOWNLOADED',
description: 'APK update corrupted file downloaded'
},
FA_APK_UPDATE_INSTALL_SUCCESS: {
name: 'FA_APK_UPDATE_INSTALL_SUCCESS',
description: 'APK update installation success'
}
} as const;
export enum MimeType {

View File

@@ -0,0 +1,29 @@
import { createSlice } from '@reduxjs/toolkit';
interface IAppUpdateSlice {
shouldUpdate: {
newApkCachedUrl: string;
switchToFallback: boolean;
};
}
const initialState: IAppUpdateSlice = {
shouldUpdate: {
newApkCachedUrl: '',
switchToFallback: false,
},
};
const AppUpdateSlice = createSlice({
name: 'appUpdate',
initialState,
reducers: {
setShouldUpdate: (state, action) => {
state.shouldUpdate = action.payload;
},
},
});
export const { setShouldUpdate } = AppUpdateSlice.actions;
export default AppUpdateSlice.reducer;

View File

@@ -16,6 +16,7 @@ import repaymentsSlice from '../reducer/repaymentsSlice';
import feedbackHistorySlice from '../reducer/feedbackHistorySlice';
import notificationsSlice from '../reducer/notificationsSlice';
import MetadataSlice from '../reducer/metadataSlice';
import AppUpdateSlice from '../reducer/appUpdateSlice';
import foregroundServiceSlice from '../reducer/foregroundServiceSlice';
import feedbackImagesSlice from '../reducer/feedbackImagesSlice';
import configSlice from '../reducer/configSlice';
@@ -47,6 +48,7 @@ const rootReducer = combineReducers({
address: addressSlice,
notifications: notificationsSlice,
metadata: MetadataSlice,
appUpdate: AppUpdateSlice,
foregroundService: foregroundServiceSlice,
feedbackImages: feedbackImagesSlice,
config: configSlice,
@@ -86,7 +88,8 @@ const persistConfig = {
'foregroundService',
'feedbackFilters',
'litmusExperiment',
'activeCall'
'activeCall',
'appUpdate'
],
blackList: [
'case',