TP-84794 | Deprecate Watermelon DB (#939)

This commit is contained in:
Mantri Ramkishor
2024-09-22 17:36:19 +05:30
committed by GitHub
23 changed files with 98 additions and 825 deletions

View File

@@ -124,7 +124,7 @@ public class MainApplication extends Application implements ReactApplication {
try {
Field field = CursorWindow.class.getDeclaredField("sCursorWindowSize");
field.setAccessible(true);
field.set(null, 20 * 1024 * 1024); // 20MB
field.set(null, 40 * 1024 * 1024); // 40MB
} catch (Exception e) {
if (BuildConfig.DEBUG) {
e.printStackTrace();

View File

@@ -6,7 +6,6 @@ import axiosInstance, {
isAxiosError,
} from '../components/utlis/apiHelper';
import { navigateToScreen } from '../components/utlis/navigationUtlis';
import OfflineImageDAO from '../wmDB/dao/OfflineImageDAO';
import {
resetSelectedTodoList,
resetTodoList,
@@ -105,7 +104,6 @@ export const syncCaseDetail =
offlineCaseKey: payload.data.offlineCaseKey,
})
);
OfflineImageDAO.deleteImages(offlineImageIdList);
toast({
type: 'success',
text1: ToastMessages.FEEDBACK_SUCCESSFUL,
@@ -117,6 +115,7 @@ export const syncCaseDetail =
.catch((errObj) => {
const statusCode = errObj?.response?.status;
const errorCode = errObj?.response?.data?.error_code;
if (isAxiosError(errObj) && statusCode !== API_STATUS_CODE.UNPROCESSABLE_CONTENT) {
toast({
type: 'error',
@@ -186,11 +185,11 @@ export const getSignedApi = async (
return new Promise((res) => {
if (shouldBatch) {
batchSignedApiRequest(signedRequestPayload, (results: any) => {
res({ imageUrl: results?.[signedRequestPayload[0].documentReferenceId] || '' });
res({ imageUrl: results?.[signedRequestPayload[0].documentReferenceId] || '' });
}, skipFirebaseUpdate);
} else {
makeBulkSignedApiRequest(signedRequestPayload, (results: any) => {
res({ imageUrl: results?.[signedRequestPayload[0].documentReferenceId] || '' });
res({ imageUrl: results?.[signedRequestPayload[0].documentReferenceId] || '' });
}, skipFirebaseUpdate);
}
});

View File

@@ -1294,6 +1294,14 @@ export const CLICKSTREAM_EVENT_NAMES = {
description: 'Feedback nudge closed'
},
FA_FEEDBACK_IMAGE_NOT_FOUND: {
name: 'FA_FEEDBACK_IMAGE_NOT_FOUND',
description: 'Feedback image not found'
},
FA_UNSYNC_FEEDBACK_CAPTURED: {
name: 'FA_UNSYNC_FEEDBACK_CAPTURED',
description: 'Unsync feedback captured'
},
} as const;
export enum MimeType {

View File

@@ -62,7 +62,7 @@ const AddressSelection: React.FC<IAddressSelection> = (props) => {
);
const caseType = currentCase?.caseType || CaseAllocationType.ADDRESS_VERIFICATION_CASE;
const template = useAppSelector((state) => state.case.templateData[caseType]);
const question = template.questions[questionId];
const question = template?.questions?.[questionId];
const dispatch = useAppDispatch();

View File

@@ -1,290 +0,0 @@
import React, { useEffect, useState } from 'react';
import { Control, Controller } from 'react-hook-form';
import {
ActivityIndicator,
ImageBackground,
StyleSheet,
TouchableOpacity,
View,
} from 'react-native';
import PhotoUpload, {
IImageDetails,
LAUNCH_REQUEST,
} from '../../../../RN-UI-LIB/src/components/photoUpload/PhotoUpload';
import { useSelector } from 'react-redux';
import Text from '../../../../RN-UI-LIB/src/components/Text';
import DeleteIcon from '../../../../RN-UI-LIB/src/Icons/DeleteIcon';
import { GenericStyles } from '../../../../RN-UI-LIB/src/styles';
import { COLORS } from '../../../../RN-UI-LIB/src/styles/colors';
import { useAppDispatch, useAppSelector } from '../../../hooks';
import { RootState } from '../../../store/store';
import { AnswerType } from '../interface';
import ErrorMessage from './ErrorMessage';
import withObservables from '@nozbe/with-observables';
import OfflineImageDAO from '../../../wmDB/dao/OfflineImageDAO';
import {
CLICKSTREAM_EVENT_NAMES,
PrefixJpegBase64Image,
getPrefixBase64Image,
} from '../../../common/Constants';
import { addClickstreamEvent } from '../../../services/clickstreamEventService';
import { isQuestionMandatory, validateInput } from '../services/validation.service';
import { CaseAllocationType } from '../../../screens/allCases/interface';
import {
addIntermediateDocument,
deleteIntermediateDocument,
} from '../../../reducer/feedbackImagesSlice';
interface IOfflineImage {
idx: string;
imageData: string;
originalImageUri: string;
}
interface IImageUpload {
questionType: string;
questionId: string;
widgetId: string;
journeyId: string;
caseId: string;
sectionId: string;
control: Control<any, any>;
error: any;
offlineImages: IOfflineImage[];
}
const getImageUri = (imageList: IOfflineImage[], imageId: string) => {
const image = imageList?.find((image) => image?.idx === imageId);
let uri = '';
if (image) {
const { originalImageUri, imageData } = image || {};
uri = originalImageUri || imageData || '';
}
return uri;
};
const ImageUpload: React.FC<IImageUpload> = (props) => {
const { questionId, error, sectionId, caseId, journeyId, widgetId, questionType } = props;
const [imageId, setImageId] = useState('');
const [loading, setImageLoading] = useState(false);
const caseType = useAppSelector(
(state) =>
state.allCases.caseDetails[caseId]?.caseType || CaseAllocationType.ADDRESS_VERIFICATION_CASE
);
const template = useAppSelector((state) => state.case.templateData[caseType]);
const question = template.questions[questionId as keyof typeof template.questions];
const dataFromRedux = useSelector(
(state: RootState) =>
state.case.caseForm?.[caseId]?.[journeyId]?.widgetContext?.[widgetId]?.sectionContext?.[
sectionId
]?.questionContext?.[questionId]?.answer
);
const dispatch = useAppDispatch();
useEffect(() => {
if (dataFromRedux) {
setImageId(dataFromRedux);
}
}, [dataFromRedux]);
if (!question) {
return null;
}
const addOriginalFileUriToDocs = (caseId: string, fileUri: string, questionKey: string) => {
if (!fileUri) {
return;
}
dispatch(addIntermediateDocument({ caseId, fileUri, questionKey }));
};
const addImageUploadSuccessClickstream = (openRequest: LAUNCH_REQUEST) => {
if (openRequest === LAUNCH_REQUEST.CAMERA) {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_FEEDBACK_PHOTO_CAPTURED_SUCCESSFULLY, {
caseId,
questionType,
questionId,
sectionId,
widgetId,
});
} else if (openRequest === LAUNCH_REQUEST.IMAGE_LIBRARY) {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_FEEDBACK_PHOTO_UPLOADED_SUCCESSFULLY, {
caseId,
questionType,
questionId,
sectionId,
widgetId,
});
}
};
const handleChange = async (clickedImage: IImageDetails, onChange: (...event: any[]) => void) => {
const { base64 = '', uri = '', openRequest } = clickedImage;
addImageUploadSuccessClickstream(openRequest);
addOriginalFileUriToDocs(caseId, uri, questionId);
if (!base64) {
return;
}
const base64Image = `${PrefixJpegBase64Image}${base64}`;
var uniqueId = 'id' + new Date().getTime();
setImageId(uniqueId);
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.AV_FORM_ELEMENT_CHANGED, {
caseId,
questionType,
value: uniqueId,
questionId,
error,
sectionId,
widgetId,
});
onChange({
answer: uniqueId,
type: AnswerType.image,
});
await OfflineImageDAO.addImage(base64Image, uri, uniqueId);
};
const handleImageDelete = () => {
setImageId('');
dispatch(deleteIntermediateDocument({ caseId, questionKey: questionId }));
};
const handleError = (error: string) => {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_PHOTO_UPLOAD_ERROR, {
caseId,
questionType,
questionId,
error,
sectionId,
widgetId,
});
};
const handlePictureAction = (openRequest: LAUNCH_REQUEST) => {
if (openRequest === LAUNCH_REQUEST.CAMERA) {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_FEEDBACK_CLICKED_TAKE_PHOTO, {
caseId,
questionType,
questionId,
sectionId,
widgetId,
});
} else if (openRequest === LAUNCH_REQUEST.IMAGE_LIBRARY) {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_FEEDBACK_CLICKED_UPLOAD_PHOTO, {
caseId,
questionType,
questionId,
sectionId,
widgetId,
});
}
};
// TODO : add the validator back when firestore is fixed.
return (
<View style={[GenericStyles.mt12]}>
<Text dark bold style={GenericStyles.mb4}>
{question.text}{' '}
{isQuestionMandatory(question) && <Text style={GenericStyles.redText}>*</Text>}
</Text>
{!imageId ? (
<Controller
control={props.control}
rules={{ validate: (data) => validateInput(data, question.metadata.validators) }}
render={({ field: { onChange } }) => (
<PhotoUpload
onPictureAction={handlePictureAction}
onPictureClickSuccess={(clickedImage) => handleChange(clickedImage, onChange)}
showUploadFromGalleryOption={true}
containerStyle={[GenericStyles.containerStyle, loading ? styles.displayNone : null]}
imageLoading={setImageLoading}
onError={handleError}
/>
)}
name={`widgetContext.${widgetId}.sectionContext.${props.sectionId}.questionContext.${questionId}`}
/>
) : (
<View>
<ImageBackground
style={styles.image}
imageStyle={styles.br8}
source={{
uri: getImageUri(props.offlineImages, imageId),
}}
onError={(error) => handleError('Error in image rendering')}
// onLoadStart={() => setImageLoading(true)}
// onLoadEnd={() => setImageLoading(false)}
>
<TouchableOpacity onPress={handleImageDelete} style={styles.deleteButton}>
<DeleteIcon />
</TouchableOpacity>
{loading ? (
<ActivityIndicator
size={'small'}
style={styles.centerAllign}
color={COLORS.TEXT.BLUE}
/>
) : null}
</ImageBackground>
</View>
)}
<ErrorMessage
show={
error?.widgetContext?.[widgetId]?.sectionContext?.[sectionId]?.questionContext?.[
questionId
]
}
/>
<ImageBackground
style={[styles.image, loading ? styles.loadingState : styles.displayNone]}
imageStyle={styles.br8}
>
<ActivityIndicator size={'small'} color={COLORS.TEXT.BLUE} />
</ImageBackground>
</View>
);
};
const styles = StyleSheet.create({
image: {
width: '100%',
height: 350,
resizeMode: 'contain',
},
deleteButton: {
position: 'absolute',
right: 8,
top: 8,
backgroundColor: COLORS.BACKGROUND.PRIMARY,
height: 28,
width: 28,
borderRadius: 14,
justifyContent: 'center',
alignItems: 'center',
borderWidth: 1,
borderColor: COLORS.BORDER.PRIMARY,
},
br8: {
borderRadius: 8,
},
displayNone: {
display: 'none',
},
loadingState: {
justifyContent: 'center',
backgroundColor: COLORS.BACKGROUND.PRIMARY,
alignItems: 'center',
borderRadius: 8,
},
centerAllign: {
position: 'absolute',
top: '50%',
left: '50%',
},
});
const enhance = withObservables([], () => ({
offlineImages: OfflineImageDAO.observeOfflineImage(),
}));
export default enhance(ImageUpload);

View File

@@ -10,7 +10,6 @@ import { useAppDispatch, useAppSelector } from '../../../../hooks';
import { RootState } from '../../../../store/store';
import { AnswerType } from '../../interface';
import ErrorMessage from '../ErrorMessage';
import OfflineImageDAO from '../../../../wmDB/dao/OfflineImageDAO';
import { CLICKSTREAM_EVENT_NAMES, PrefixJpegBase64Image } from '../../../../common/Constants';
import { addClickstreamEvent, ClickstreamDesc } from '../../../../services/clickstreamEventService';
import { isQuestionMandatory, validateInput } from '../../services/validation.service';
@@ -205,7 +204,6 @@ const ImageUploadV2: React.FC<IImageUpload> = (props) => {
answer: uniqueId,
type: AnswerType.image,
});
await OfflineImageDAO.addImage(base64Image, uri, uniqueId, imageWidth, imageHeight);
toast({ type: 'success', text1: 'Geolocation & Timestamp added successfully' });
setImageLoading(false);
addOriginalFileUriToDocs(caseId, uri, questionId, imageWidth, imageHeight);
@@ -221,11 +219,7 @@ const ImageUploadV2: React.FC<IImageUpload> = (props) => {
}
};
const {
fileUri,
imageHeight = 350,
imageWidth = 350
} = imageDoc || {};
const { fileUri, imageHeight = 350, imageWidth = 350 } = imageDoc || {};
const imageHeightWrtAspectRatio = getImageHeightWrtAspectRatio(
imageWidth,
@@ -242,13 +236,12 @@ const ImageUploadV2: React.FC<IImageUpload> = (props) => {
) => {
setImageLoading(false);
const { base64 = '', uri = '', openRequest } = clickedImage;
const uniqueId = 'id' + new Date().getTime();
addImageUploadSuccessClickstream(openRequest);
addOriginalFileUriToDocs(caseId, uri, questionId);
if (!base64) {
return;
}
const base64Image = `${PrefixJpegBase64Image}${base64}`;
var uniqueId = 'id' + new Date().getTime();
setImageId(uniqueId);
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.AV_FORM_ELEMENT_CHANGED, {
caseId,
@@ -263,7 +256,7 @@ const ImageUploadV2: React.FC<IImageUpload> = (props) => {
answer: uniqueId,
type: AnswerType.image,
});
await OfflineImageDAO.addImage(base64Image, uri, uniqueId);
addOriginalFileUriToDocs(caseId, uri, questionId, imageWidth, imageHeight);
};
const handleImageUpload = async (onChange: (...event: any[]) => void) => {

View File

@@ -307,6 +307,14 @@ const Widget: React.FC<IWidget> = (props) => {
unSyncedCase,
nudgeBottomSheetDetails?.showNudgeBottomSheet
);
if(!transformedPayload?.data?.answers) {
toast({
type: 'error',
text1: ToastMessages.FEEDBACK_IMAGE_NOT_FOUND,
});
onErrorSubmit({}, transformedPayload)
return;
}
dispatch(
syncCaseDetail(transformedPayload, {
onSuccessCB: (apiCaseData, interactionId: string) =>

View File

@@ -10,7 +10,6 @@ import AddressSelection from '../components/AddressSelection';
import PhoneNumberSelection from '../components/PhoneNumberSelection';
import DateInput from '../components/DateInput';
import TimeInput from '../components/TimeInput';
import ImageUpload from '../components/ImageUpload';
export const FormComponentList = {
TextInput,

View File

@@ -49,10 +49,6 @@ export class CaptureGeolocation {
if (cachedLocation && Date.now() - (cachedLocation?.location?.timestamp || 0) < cacheTTL) {
return resolve(cachedLocation?.location?.coords);
}
if (cachedLocation && cachedLocation?.isCapturing) {
console.info('Capture already in progress. Returning');
resolve(undefined);
}
CaptureGeolocation.setCapturing(resourceId, true);
const isLocationOn = await PermissionsAndroid.check(
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION

View File

@@ -64,6 +64,7 @@ export const ToastMessages = {
SUCCESS_COPYING_EMPLOYER_NAME: 'Employer Name Copied Successfully!!',
FEEDBACK_SUCCESSFUL: 'Feedback submitted successfully!',
FEEDBACK_FAILED: 'Feedback submission failed',
FEEDBACK_IMAGE_NOT_FOUND: 'Feedback submission failed. Please try uploading image again',
FIRESTORE_SIGNIN_FAILED: 'Error signing in to Firestore',
PAYMENT_LINK_ERROR: 'Payment link could not be shared',
PAYMENT_LINK_SUCCESS: 'Link has been generated and shared with customer',

View File

@@ -4,10 +4,7 @@ import { syncCaseDetail } from '../../action/dataActions';
import { CLICKSTREAM_EVENT_NAMES } from '../../common/Constants';
import { useAppDispatch, useAppSelector } from '../../hooks';
import useIsOnline from '../../hooks/useIsOnline';
import {
getTransformedAvCase,
getTransformedCollectionCaseItem,
} from '../../services/casePayload.transformer';
import { getTransformedCollectionCaseItem } from '../../services/casePayload.transformer';
import { addClickstreamEvent } from '../../services/clickstreamEventService';
import { CaseAllocationType } from '../allCases/interface';
import { CaseDetail } from './interface';
@@ -64,12 +61,16 @@ const interactionsHandler = () => {
for (const caseItem of notSyncedCases) {
if (isOnline) {
const caseKey = caseItem.offlineCaseKey || caseItem.id;
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_UNSYNC_FEEDBACK_CAPTURED, {
caseId: caseKey,
});
inProgressCaseIds.current.push(caseKey);
let modifiedCaseItem: any;
if (caseItem?.caseType === CaseAllocationType.COLLECTION_CASE) {
modifiedCaseItem = await getTransformedCollectionCaseItem(caseItem, true);
} else {
modifiedCaseItem = await getTransformedAvCase(caseItem, templateId);
}
if (!modifiedCaseItem?.data?.answers) {
return;
}
dispatch(
syncCaseDetail(modifiedCaseItem, {

View File

@@ -1,16 +1,12 @@
import { CONTEXT_TASK_STATUSES, CaseDetail } from './../screens/caseDetails/interface';
import { CaseDetail } from './../screens/caseDetails/interface';
import { AnswerType } from '../components/form/interface';
import OfflineImageDAO from '../wmDB/dao/OfflineImageDAO';
import {
CaseAllocationType,
IAvCasePayload,
IAvTaskFeedbackItem,
IQuestionContextOutput,
TaskTitle,
} from '../screens/allCases/interface';
import { IQuestionContextOutput } from '../screens/allCases/interface';
import Geolocation from 'react-native-geolocation-service';
const AV_TEMPLATE_VERSION_NUMBER = 3;
import store from '@store';
import RNFS from 'react-native-fs';
import { addClickstreamEvent } from './clickstreamEventService';
import { CLICKSTREAM_EVENT_NAMES } from '@common/Constants';
import ImageResizer from '@bam.tech/react-native-image-resizer';
interface QuestionContext {
answer: string;
@@ -36,8 +32,18 @@ interface Answer {
allocationReferenceId: string;
}
export const extractQuestionContext = async (answer: Answer): Promise<IQuestionContextOutput[]> => {
const IMAGE_QUALITY = 50;
const MAX_WIDTH = 500;
const MAX_HEIGHT = 500;
export const extractQuestionContext = async (
answer: Answer,
caseReferenceId: string
): Promise<IQuestionContextOutput[] | null> => {
const questionContexts: IQuestionContextOutput[] = [];
let isBase64ImageAvailable = true;
const docsData =
store?.getState()?.feedbackImages?.intermediateDocsToBeUploaded?.[caseReferenceId]?.documents;
const { widgetContext } = answer;
for (const widgetKey in widgetContext) {
@@ -64,12 +70,27 @@ export const extractQuestionContext = async (answer: Answer): Promise<IQuestionC
answer = { ...answer, type: AnswerType.text };
}
if (answer.type === AnswerType.image && answer.answer?.length) {
const offlineImageId = answer.answer;
const data = await getBase64ImageFromOfflineDb(offlineImageId);
answer = {
...answer,
answer: data,
};
const imageData = docsData?.[questionKey];
try {
const response = await ImageResizer.createResizedImage(
imageData?.fileUri ?? '',
MAX_WIDTH,
MAX_HEIGHT,
'JPEG',
IMAGE_QUALITY,
undefined,
undefined,
true
);
const base64Image = await RNFS.readFile(response?.uri, 'base64');
answer = {
...answer,
answer: `data:image/jpeg;base64,${base64Image}`,
};
} catch (error) {
isBase64ImageAvailable = false;
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_FEEDBACK_IMAGE_NOT_FOUND, { error });
}
}
questionContexts.push({
answerContextDTO: answer,
@@ -79,16 +100,15 @@ export const extractQuestionContext = async (answer: Answer): Promise<IQuestionC
}
}
if (!isBase64ImageAvailable) return null;
return questionContexts;
};
export const getImageFromDB = async (imageId: string) => {
let imageList = await OfflineImageDAO.getImage(imageId);
// returns the latest image in the database
return imageList?.[0];
}
export const getImageHeightWrtAspectRatio = (imageWidth: number, imageHeight: number, requiredImageWidth: number) => {
export const getImageHeightWrtAspectRatio = (
imageWidth: number,
imageHeight: number,
requiredImageWidth: number
) => {
if (!imageWidth || !imageHeight) {
return 0;
}
@@ -96,16 +116,6 @@ export const getImageHeightWrtAspectRatio = (imageWidth: number, imageHeight: nu
return requiredImageWidth * aspectRatio;
};
export const getBase64ImageFromOfflineDb = async (imageId: string) => {
let imageList = await OfflineImageDAO.getImage(imageId);
return imageList?.[0]?.imageData;
};
export const getOriginalImageFromOfflineDb = async (imageId: string) => {
let imageList = await OfflineImageDAO.getImage(imageId);
return imageList?.[0]?.originalImageUri;
};
export interface IGetTransformedCaseItem extends CaseDetail {
answer: any;
caseId: string;
@@ -118,7 +128,10 @@ export const getTransformedCollectionCaseItem = async (
forceSubmit = false
) => {
let cloneCaseItem = { ...caseItem };
let answerContextArray = await extractQuestionContext(cloneCaseItem?.answer);
let answerContextArray = await extractQuestionContext(
cloneCaseItem?.answer,
caseItem?.caseReferenceId
);
let data = {
caseReferenceId: caseItem.caseReferenceId,
answers: answerContextArray,
@@ -129,42 +142,3 @@ export const getTransformedCollectionCaseItem = async (
};
return { caseType: caseItem.caseType, data, forceSubmit };
};
export const getTransformedAvCase = async (
caseItem: IGetTransformedCaseItem,
templateId: string
) => {
const { caseType, allocationReferenceId, caseId, coords } = caseItem;
const transformedAvCase: IAvCasePayload = {
caseType: caseType || CaseAllocationType.ADDRESS_VERIFICATION_CASE,
data: {
version: AV_TEMPLATE_VERSION_NUMBER,
templateId,
allocationReferenceId: allocationReferenceId as string,
taskFeedbacks: [],
},
};
const taskContext = caseItem?.context?.taskContext;
for (let taskId in taskContext) {
const taskItems: any[] = taskContext[taskId];
for (const taskItem of taskItems) {
const { taskStatus, createdAt } = taskItem;
if (taskId !== TaskTitle.CALLING_TASK && taskStatus === CONTEXT_TASK_STATUSES.OPEN) continue;
let taskFeedbackItem: IAvTaskFeedbackItem = {
taskType: taskId as TaskTitle,
location: coords,
answers: [],
taskStatus,
capturedAt: createdAt || new Date().getTime(),
};
const answersList = await extractQuestionContext(taskItem);
if (!answersList.length) continue;
taskFeedbackItem.answers = answersList;
transformedAvCase.data.taskFeedbacks.push(taskFeedbackItem);
}
}
return transformedAvCase;
};

View File

@@ -1,15 +1,11 @@
import { ApiKeys, API_URLS } from './../components/utlis/apiHelper';
import { API_URLS } from './../components/utlis/apiHelper';
import {
getAppVersion,
getEventNameFromAPIKey,
getKeyByValue,
getParamsObject,
} from './../components/utlis/commonFunctions';
import React from 'react';
import axiosInstance from '../components/utlis/apiHelper';
import { getNetInfo } from '../components/utlis/commonFunctions';
import { GLOBAL } from '../constants/Global';
import ClickStreamEventsDAO, { IClickstreamEvent } from '../wmDB/dao/ClickStreamEventsDAO';
import {
NetInfoCellularGeneration,
NetInfoState,
@@ -23,8 +19,20 @@ import { SCREEN_HEIGHT, SCREEN_WIDTH } from '@rn-ui-lib/styles';
export type ClickstreamDesc = { name: string; description: string };
export interface IClickstreamEvent {
event_name: string;
description: string;
timestamp: number;
agentId: string;
deviceId: string;
attributes?: unknown;
networkStatus: string;
appVersion: string;
isOnline: boolean;
batteryLevel: string;
}
const MAX_BUFFER_SIZE_FOR_API = 10;
const MAX_BUFFER_SIZE_FOR_DB_WRITE = 20;
let eventsList: IClickstreamEvent[] = [];
let bufferEventsList: IClickstreamEvent[] = [];
@@ -48,9 +56,6 @@ const addEvent = async (networkStatus: string) => {
if (eventsList.length > MAX_BUFFER_SIZE_FOR_API) {
if (networkStatus !== 'offline') {
fireClickstreamEvents();
} else if (eventsList.length > MAX_BUFFER_SIZE_FOR_DB_WRITE) {
await ClickStreamEventsDAO.addEventsBulk(eventsList);
eventsList = [];
}
}
};
@@ -122,40 +127,9 @@ const fireClickstreamEvents = async () => {
const url = JANUS_SERVICE_URL;
bufferEventsList = [...eventsList];
eventsList = [];
const payload = await getPayload(bufferEventsList);
const clickstreamEventsFromDB = await ClickStreamEventsDAO.getAllEvents();
const events = clickstreamEventsFromDB
.map((item: IClickstreamEvent) => {
const {
deviceId,
agentId,
event_name,
attributes,
timestamp,
networkStatus,
appVersion,
isOnline,
batteryLevel,
} = item;
return {
deviceId,
agentId,
event_name,
attributes,
timestamp,
networkStatus,
appVersion,
isOnline,
batteryLevel,
};
})
.concat(bufferEventsList);
const payload = await getPayload(events);
axiosInstance
.post(url, payload, { headers: { donotHandleError: true } })
.then(() => ClickStreamEventsDAO.deleteAllEvents());
axiosInstance.post(url, payload, { headers: { donotHandleError: true } });
};
export const sendApiToClickstreamEvent = (
@@ -171,7 +145,7 @@ export const sendApiToClickstreamEvent = (
const eventName = getEventNameFromAPIKey(apiKey, isSuccess);
addClickstreamEvent(
{ name: eventName, description: eventName },
{ timeTaken: milliseconds, statusCode, response: statusCode === 400 ? response : '' }
{ timeTaken: milliseconds, statusCode, response: statusCode === 400 ? response : '' }
);
}
}

View File

@@ -1,9 +0,0 @@
export enum TableName {
OFFLINE_IMAGES = 'offline_image',
CUSTOMER_IMAGE = 'customer_image',
CLICKSTREAM_EVENTS = 'clickstream_events',
}
export const DB_VERSION = 6;
export const DB_NAME = 'AVAPP';

View File

@@ -1,116 +0,0 @@
import { database } from '../db';
import { Q } from '@nozbe/watermelondb';
import { TableName } from '../const';
import { logError } from '../../components/utlis/errorUtils';
const clickStreamEvents = database.get(TableName.CLICKSTREAM_EVENTS);
export interface IClickstreamEvent {
event_name: string;
description: string;
timestamp: number;
agentId: string;
deviceId: string;
attributes?: unknown;
networkStatus: string;
appVersion: string;
isOnline: boolean;
batteryLevel: string;
}
export default {
observeOfflineImage: () => clickStreamEvents.query().observe(),
addEvent: async (
event_name: string,
timestamp: number,
description: string,
agentId: string,
deviceId: string,
attributes: any,
networkStatus: string,
appVersion: string,
isOnline: boolean,
batteryLevel: string
) => {
try {
return await database.action(async () => {
return await clickStreamEvents.create((event: any) => {
event.event_name = event_name;
event.description = description;
event.timestamp = timestamp;
event.deviceId = deviceId;
event.agentId = agentId;
event.attributes = attributes;
event.networkStatus = networkStatus;
event.appVersion = appVersion;
event.isOnline = isOnline;
event.batteryLevel = batteryLevel;
});
});
} catch (e) {
console.log(e);
}
},
addEventsBulk: async (events: Array<IClickstreamEvent>) => {
function prepareInsertion(events: Array<IClickstreamEvent>) {
return events.map((clickStreamEvent) => {
const {
event_name,
deviceId,
attributes,
agentId,
timestamp,
description,
networkStatus,
isOnline,
appVersion,
batteryLevel,
} = clickStreamEvent;
try {
return clickStreamEvents.prepareCreate((event: any) => {
event.event_name = event_name;
event.description = description;
event.timestamp = timestamp;
event.deviceId = deviceId;
event.agentId = agentId;
event.attributes = attributes;
event.networkStatus = networkStatus;
event.isOnline = isOnline;
event.appVersion = appVersion;
event.batteryLevel = batteryLevel;
});
} catch (e) {
logError(e as Error, 'WM DB');
}
});
}
try {
return await database.action(async () => {
const allRecords = prepareInsertion(events);
await database.batch(...allRecords);
});
} catch (e) {
logError(e as Error, 'WM DB');
}
},
getAllEvents: async () => {
try {
return await database.action(async () => {
return await clickStreamEvents.query().fetch();
});
} catch (e) {
console.log(e);
return;
}
},
deleteAllEvents: async () => {
try {
return await database.action(async () => {
await clickStreamEvents.query().destroyAllPermanently();
});
} catch (e) {
console.log(e);
return;
}
},
};

View File

@@ -1,93 +0,0 @@
import { database } from '../db';
import { Q } from '@nozbe/watermelondb';
import { TableName } from '../const';
import { logError } from '../../components/utlis/errorUtils';
const customerImage = database.get(TableName.CUSTOMER_IMAGE);
interface ICustomerOfflineImage {
caseId: string;
imageURL: string;
image64: string;
}
export default {
observeOfflineImage: () => customerImage.query().observe(),
addBulkCustomerImage: async (customerImageList: ICustomerOfflineImage[]) => {
function prepareInsertion(customerImageList: ICustomerOfflineImage[]) {
return customerImageList.map((customerImageItem) => {
try {
return customerImage.prepareCreate((imageRow: any) => {
imageRow.caseId = customerImageItem.caseId;
imageRow.imageURL = customerImageItem.imageURL;
imageRow.image64 = customerImageItem.image64;
});
} catch (e) {
logError(e as Error, 'WM DB');
}
});
}
try {
return await database.action(async () => {
const allRecords = prepareInsertion(customerImageList);
await database.batch(...allRecords);
});
} catch (e) {
logError(e as Error, 'WM DB');
}
},
addCustomerImage: async (customerImageItem: ICustomerOfflineImage) => {
try {
return await database.action(async () => {
return await customerImage.create((imageRow: any) => {
imageRow.caseId = customerImageItem.caseId;
imageRow.imageURL = customerImageItem.imageURL;
imageRow.image64 = customerImageItem.image64;
});
});
} catch (e) {
logError(e as Error, 'WM DB');
}
},
getImageByURL: async (imageURL: string) => {
try {
return await database.action(async () => {
return await customerImage.query(Q.where('image_url', imageURL), Q.take(1)).fetch();
});
} catch (e) {
logError(e as Error, 'WM DB');
return;
}
},
getImageByCase: async (caseId: string) => {
try {
return await database.action(async () => {
return await customerImage.query(Q.where('case_id', caseId), Q.take(1)).fetch();
});
} catch (e) {
logError(e as Error, 'WM DB');
return;
}
},
deleteImages: async (caseIds: Array<string> | string) => {
let tCaseIds = Array.isArray(caseIds) ? caseIds : [caseIds];
try {
return await database.action(async () => {
await customerImage.query(Q.where('case_id', Q.oneOf(tCaseIds))).destroyAllPermanently();
});
} catch (e) {
logError(e as Error, 'WM DB');
return;
}
},
deleteAll: async () => {
try {
return await database.action(async () => {
await customerImage.query().destroyAllPermanently();
});
} catch (e) {
logError(e as Error, 'WM DB');
return;
}
},
};

View File

@@ -1,60 +0,0 @@
import { database } from '../db';
import { Q } from '@nozbe/watermelondb';
import { TableName } from '../const';
const offlineImage = database.get(TableName.OFFLINE_IMAGES);
export default {
observeOfflineImage: () => offlineImage.query().observe(),
addImage: async (
imageData: string,
originalImageUri: string,
imageIdx: string,
imageWidth: number,
imageHeight: number
) => {
try {
return await database.action(async () => {
return await offlineImage.create((image: any) => {
image.idx = imageIdx;
image.imageData = imageData;
image.originalImageUri = originalImageUri;
image.imageWidth = imageWidth;
image.imageHeight = imageHeight;
});
});
} catch (e) {
console.log(e);
}
},
getImage: async (imageId: string) => {
try {
return await database.action(async () => {
return await offlineImage.query(Q.where('idx', imageId), Q.take(1)).fetch();
});
} catch (e) {
console.log(e);
return;
}
},
deleteImages: async (imageIdList: Array<string>) => {
try {
return await database.action(async () => {
await offlineImage.query(Q.where('idx', Q.oneOf(imageIdList))).destroyAllPermanently();
});
} catch (e) {
console.log(e);
return;
}
},
deleteAll: async () => {
try {
return await database.action(async () => {
await offlineImage.query().destroyAllPermanently();
});
} catch (e) {
console.log(e);
return;
}
},
};

View File

@@ -1,20 +0,0 @@
import { Database } from '@nozbe/watermelondb';
import SQLiteAdapter from '@nozbe/watermelondb/adapters/sqlite';
import { DB_NAME } from './const';
import ClickstreamEvents from './model/ClickstreamEvents';
import CustomerImage from './model/CustomerImage';
import OfflineImage from './model/OfflineImage';
import schema from './schema';
const adapter = new SQLiteAdapter({
dbName: DB_NAME,
schema,
});
const database = new Database({
adapter,
modelClasses: [OfflineImage, CustomerImage, ClickstreamEvents],
});
export { database };

View File

@@ -1,6 +0,0 @@
import PostDAO from './dao/OfflineImageDAO';
import CustomerImageDAO from './dao/CustomerImageDAO';
import ClickstreamDAO from './dao/ClickStreamEventsDAO';
export { database } from './db';
export { PostDAO, CustomerImageDAO, ClickstreamDAO };

View File

@@ -1,20 +0,0 @@
import { Model } from '@nozbe/watermelondb';
import { field, json } from '@nozbe/watermelondb/decorators';
import { TableName } from '../const';
const sanitizeAttributes = (json: any) => json;
export default class ClickstreamEvents extends Model {
static table = TableName.CLICKSTREAM_EVENTS;
@field('event_name') event_name!: string;
@field('description') description!: string;
@field('agent_id') agentId!: string;
@field('device_id') deviceId!: string;
@field('network_status') networkStatus!: string;
@field('timestamp') timestamp!: number;
@json('attributes', sanitizeAttributes) attributes: any;
@field('app_version') appVersion!: string;
@field('is_online') isOnline!: boolean;
@field('battery_level') batteryLevel!: string;
}

View File

@@ -1,13 +0,0 @@
import { Model } from '@nozbe/watermelondb';
import { field, readonly, date } from '@nozbe/watermelondb/decorators';
import { TableName } from '../const';
export default class CustomerImage extends Model {
static table = TableName.CUSTOMER_IMAGE;
@field('case_id') caseId!: string;
@field('image_url') imageURL!: string;
@field('image_64') image64!: string;
@readonly @date('created_at') createdAt!: any;
@readonly @date('updated_at') updatedAt!: any;
}

View File

@@ -1,15 +0,0 @@
import { Model } from '@nozbe/watermelondb';
import { field, readonly, date } from '@nozbe/watermelondb/decorators';
import { TableName } from '../const';
export default class OfflineImage extends Model {
static table = TableName.OFFLINE_IMAGES;
@field('idx') idx!: string;
@field('image_data') imageData!: string;
@field('original_image_uri') originalImageUri!: string;
@field('image_width') imageWidth!: string;
@field('image_height') imageHeight!: string;
@readonly @date('created_at') createdAt!: any;
@readonly @date('updated_at') updatedAt!: any;
}

View File

@@ -1,38 +0,0 @@
import { appSchema, tableSchema } from '@nozbe/watermelondb';
import { DB_VERSION, TableName } from './const';
export default appSchema({
version: DB_VERSION,
tables: [
tableSchema({
name: TableName.OFFLINE_IMAGES,
columns: [
{ name: 'idx', type: 'string' },
{ name: 'image_data', type: 'string' },
{ name: 'original_image_uri', type: 'string' },
{ name: 'image_width', type: 'string' },
{ name: 'image_height', type: 'string' },
],
}),
tableSchema({
name: TableName.CUSTOMER_IMAGE,
columns: [
{ name: 'case_id', type: 'string' },
{ name: 'image_url', type: 'string' },
{ name: 'image_64', type: 'string' },
],
}),
tableSchema({
name: TableName.CLICKSTREAM_EVENTS,
columns: [
{ name: 'event_name', type: 'string' },
{ name: 'description', type: 'string' },
{ name: 'agent_id', type: 'string' },
{ name: 'device_id', type: 'string' },
{ name: 'attributes', type: 'string' },
{ name: 'network_status', type: 'string' },
{ name: 'timestamp', type: 'number' },
],
}),
],
});