Skip to content

feat: Added Offline storage support to Event processor for React Native Apps #517

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 29 commits into from
Jul 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
091aaf2
first implementation of offline events. Need to do code cleanup
zashraf1985 Jul 2, 2020
8076ef4
added new `eventMaxQueueSize` configuration option.
zashraf1985 Jul 3, 2020
6d338d2
some cleanup and refactor
zashraf1985 Jul 3, 2020
ab29e0e
added some comments
zashraf1985 Jul 3, 2020
cbb6aef
added connectivity listener
zashraf1985 Jul 4, 2020
b26d407
fixed existing unit tests
zashraf1985 Jul 6, 2020
edbfaba
Merge branch 'master' into zeeshan/rn-event-processor-proto
zashraf1985 Jul 6, 2020
0621882
sequences the pending events one after the other
zashraf1985 Jul 8, 2020
e94ebdc
Sequenced the buffered events after pending events on the start
zashraf1985 Jul 8, 2020
35b5d4f
added some extra checks to fix the behavior of stop
zashraf1985 Jul 9, 2020
7db47a6
modified the resolvable promise to make it work with jest
zashraf1985 Jul 9, 2020
d99fb66
1. added await while clearing buffer store
zashraf1985 Jul 9, 2020
9918024
re factored the hierarchy of event processor classes
zashraf1985 Jul 10, 2020
8018de0
refactored how pending events are dispatched. simplified the whole ev…
zashraf1985 Jul 10, 2020
134c5df
removed localhost url
zashraf1985 Jul 10, 2020
8467a91
fixed existing unit tests
zashraf1985 Jul 10, 2020
a49a6fa
renamed file to reflect its react native version of event processor
zashraf1985 Jul 10, 2020
a19f66f
simplified synchronizer
zashraf1985 Jul 13, 2020
bb7024e
using only one store for both pending events and events buffer
zashraf1985 Jul 13, 2020
01ef03e
Update packages/event-processor/src/v1/v1EventProcessor.react_native.ts
zashraf1985 Jul 13, 2020
3411d52
added some logs
zashraf1985 Jul 14, 2020
108fea6
Added unit tests for React Native store
zashraf1985 Jul 14, 2020
8dadf85
incorporated review feedback from jae and owais
zashraf1985 Jul 16, 2020
b5267fb
Added skeleton for react native event processor unit tests. Its not c…
zashraf1985 Jul 16, 2020
6c3c40d
moved cache code from utils to this repo to resolve dependency issues
zashraf1985 Jul 17, 2020
a028ffd
added unit tests and fixed some issues found during unit testing
zashraf1985 Jul 17, 2020
b92d95d
added more test and fixed an issue with buffer store
zashraf1985 Jul 18, 2020
2b1594d
added more tests
zashraf1985 Jul 19, 2020
dd7c837
reduced timeouts to make tests faster
zashraf1985 Jul 19, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* Copyright 2020, Optimizely
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
let items: {[key: string]: string} = {}

export default class AsyncStorage {

static getItem(key: string, callback?: (error?: Error, result?: string) => void): Promise<string | null> {
return new Promise(resolve => {
setTimeout(() => resolve(items[key] || null), 1)
})
}

static setItem(key: string, value: string, callback?: (error?: Error) => void): Promise<void> {
return new Promise((resolve) => {
setTimeout(() => {
items[key] = value
resolve()
}, 1)
})
}

static removeItem(key: string, callback?: (error?: Error, result?: string) => void): Promise<string | null> {
return new Promise(resolve => {
setTimeout(() => {
items[key] && delete items[key]
resolve()
}, 1)
})
}

static dumpItems(): {[key: string]: string} {
return items
}

static clearStore(): void {
items = {}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Copyright 2020, Optimizely
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
let localCallback: any

export function addEventListener(callback: any) {
localCallback = callback
}

export function triggerInternetState(isInternetReachable: boolean) {
localCallback({ isInternetReachable })
}
219 changes: 219 additions & 0 deletions packages/event-processor/__tests__/reactNativeEventsStore.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
/**
* Copyright 2020, Optimizely
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// <reference types="jest" />

import { ReactNativeEventsStore } from '../src/reactNativeEventsStore'
import AsyncStorage from '../__mocks__/@react-native-community/async-storage'

const STORE_KEY = 'test-store'

describe('ReactNativeEventsStore', () => {
let store: ReactNativeEventsStore<any>

beforeEach(() => {
store = new ReactNativeEventsStore(5, STORE_KEY)
})

describe('set', () => {
it('should store all the events correctly in the store', async () => {
await store.set('event1', {'name': 'event1'})
await store.set('event2', {'name': 'event2'})
await store.set('event3', {'name': 'event3'})
await store.set('event4', {'name': 'event4'})
const storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY])
expect(storedPendingEvents).toEqual({
"event1": { "name": "event1" },
"event2": { "name": "event2" },
"event3": { "name": "event3" },
"event4": { "name": "event4" },
})
})

it('should store all the events when set asynchronously', async (done) => {
const promises = []
promises.push(store.set('event1', {'name': 'event1'}))
promises.push(store.set('event2', {'name': 'event2'}))
promises.push(store.set('event3', {'name': 'event3'}))
promises.push(store.set('event4', {'name': 'event4'}))
Promise.all(promises).then(() => {
const storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY])
expect(storedPendingEvents).toEqual({
"event1": { "name": "event1" },
"event2": { "name": "event2" },
"event3": { "name": "event3" },
"event4": { "name": "event4" },
})
done()
})
})
})

describe('get', () => {
it('should correctly get items', async () => {
await store.set('event1', {'name': 'event1'})
await store.set('event2', {'name': 'event2'})
await store.set('event3', {'name': 'event3'})
await store.set('event4', {'name': 'event4'})
expect(await store.get('event1')).toEqual({'name': 'event1'})
expect(await store.get('event2')).toEqual({'name': 'event2'})
expect(await store.get('event3')).toEqual({'name': 'event3'})
expect(await store.get('event4')).toEqual({'name': 'event4'})
})
})

describe('getEventsMap', () => {
it('should get the whole map correctly', async () => {
await store.set('event1', {'name': 'event1'})
await store.set('event2', {'name': 'event2'})
await store.set('event3', {'name': 'event3'})
await store.set('event4', {'name': 'event4'})
const mapResult = await store.getEventsMap()
expect(mapResult).toEqual({
"event1": { "name": "event1" },
"event2": { "name": "event2" },
"event3": { "name": "event3" },
"event4": { "name": "event4" },
})
})
})

describe('getEventsList', () => {
it('should get all the events as a list', async () => {
await store.set('event1', {'name': 'event1'})
await store.set('event2', {'name': 'event2'})
await store.set('event3', {'name': 'event3'})
await store.set('event4', {'name': 'event4'})
const listResult = await store.getEventsList()
expect(listResult).toEqual([
{ "name": "event1" },
{ "name": "event2" },
{ "name": "event3" },
{ "name": "event4" },
])
})
})

describe('remove', () => {
it('should correctly remove items from the store', async () => {
await store.set('event1', {'name': 'event1'})
await store.set('event2', {'name': 'event2'})
await store.set('event3', {'name': 'event3'})
await store.set('event4', {'name': 'event4'})
let storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY])
expect(storedPendingEvents).toEqual({
"event1": { "name": "event1" },
"event2": { "name": "event2" },
"event3": { "name": "event3" },
"event4": { "name": "event4" },
})

await store.remove('event1')
storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY])
expect(storedPendingEvents).toEqual({
"event2": { "name": "event2" },
"event3": { "name": "event3" },
"event4": { "name": "event4" },
})

await store.remove('event2')
storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY])
expect(storedPendingEvents).toEqual({
"event3": { "name": "event3" },
"event4": { "name": "event4" },
})
})

it('should correctly remove items from the store when removed asynchronously', async (done) => {
await store.set('event1', {'name': 'event1'})
await store.set('event2', {'name': 'event2'})
await store.set('event3', {'name': 'event3'})
await store.set('event4', {'name': 'event4'})
let storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY])
expect(storedPendingEvents).toEqual({
"event1": { "name": "event1" },
"event2": { "name": "event2" },
"event3": { "name": "event3" },
"event4": { "name": "event4" },
})

const promises = []
promises.push(store.remove('event1'))
promises.push(store.remove('event2'))
promises.push(store.remove('event3'))
Promise.all(promises).then(() => {
let storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY])
expect(storedPendingEvents).toEqual({ "event4": { "name": "event4" }})
done()
})
})
})

describe('clear', () => {
it('should clear the whole store',async () => {
await store.set('event1', {'name': 'event1'})
await store.set('event2', {'name': 'event2'})
await store.set('event3', {'name': 'event3'})
await store.set('event4', {'name': 'event4'})
let storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY])
expect(storedPendingEvents).toEqual({
"event1": { "name": "event1" },
"event2": { "name": "event2" },
"event3": { "name": "event3" },
"event4": { "name": "event4" },
})
await store.clear()
storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY] || '{}')
expect(storedPendingEvents).toEqual({})
})
})

describe('maxSize', () => {
it('should not add anymore events if the store if full', async () => {
await store.set('event1', {'name': 'event1'})
await store.set('event2', {'name': 'event2'})
await store.set('event3', {'name': 'event3'})
await store.set('event4', {'name': 'event4'})

let storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY])
expect(storedPendingEvents).toEqual({
"event1": { "name": "event1" },
"event2": { "name": "event2" },
"event3": { "name": "event3" },
"event4": { "name": "event4" },
})
await store.set('event5', {'name': 'event5'})

storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY])
expect(storedPendingEvents).toEqual({
"event1": { "name": "event1" },
"event2": { "name": "event2" },
"event3": { "name": "event3" },
"event4": { "name": "event4" },
"event5": { "name": "event5" },
})

await store.set('event6', {'name': 'event6'})
storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY])
expect(storedPendingEvents).toEqual({
"event1": { "name": "event1" },
"event2": { "name": "event2" },
"event3": { "name": "event3" },
"event4": { "name": "event4" },
"event5": { "name": "event5" },
})
})
})
})
Loading