Skip to content

Commit 4b5ef80

Browse files
authored
Merge pull request #170 from Resgrid/develop
CU-868ffcgaz Countly implementation
2 parents 5c19fee + 02bf815 commit 4b5ef80

25 files changed

+1029
-442
lines changed

__mocks__/@aptabase/react-native.ts

Lines changed: 0 additions & 5 deletions
This file was deleted.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* Mock for Countly React Native SDK
3+
* Used during testing to prevent actual analytics calls
4+
*/
5+
6+
const mockCountly = {
7+
init: jest.fn().mockResolvedValue(undefined),
8+
start: jest.fn().mockResolvedValue(undefined),
9+
enableCrashReporting: jest.fn().mockResolvedValue(undefined),
10+
events: {
11+
recordEvent: jest.fn().mockResolvedValue(undefined),
12+
},
13+
};
14+
15+
export default mockCountly;

docs/countly-migration.md

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
# Analytics Migration: Aptabase to Countly
2+
3+
## Migration Completed
4+
5+
This project has been successfully migrated from Aptabase to Countly for analytics tracking while maintaining full backward compatibility.
6+
7+
## Previous Aptabase Implementation
8+
9+
Previously, the application used Aptabase for analytics with comprehensive error handling:
10+
11+
- **Aptabase Service**: Centralized error handling and retry logic
12+
- **Aptabase Provider Wrapper**: Error-safe provider with fallback rendering
13+
- **Simple Configuration**: Single app key configuration
14+
15+
## Current Countly Implementation
16+
17+
### 1. Countly Service (`src/services/analytics.service.ts`)
18+
19+
- **Purpose**: Centralized analytics tracking with error handling
20+
- **Features**:
21+
- Simple event tracking interface compatible with previous Aptabase interface
22+
- Graceful error handling with retry logic
23+
- Automatic service disable/enable after failures
24+
- Comprehensive logging of events and errors
25+
- Converts event properties to Countly segmentation format
26+
27+
### 2. Countly Provider Wrapper (`src/components/common/aptabase-provider.tsx`)
28+
29+
- **Purpose**: Initializes Countly SDK with error handling
30+
- **Features**:
31+
- Safe initialization with error recovery
32+
- Uses the Countly service for error management
33+
- Always renders children (no provider wrapper required)
34+
- Configurable with app key and server URL
35+
- Backward compatible `AptabaseProviderWrapper` export
36+
37+
### 3. Updated Layout (`src/app/_layout.tsx`)
38+
39+
- **Purpose**: Uses the new Countly wrapper with updated configuration
40+
- **Change**: Updated to pass both `COUNTLY_APP_KEY` and `COUNTLY_SERVER_URL`
41+
42+
## Key Benefits of Migration
43+
44+
### Enhanced Analytics Capabilities
45+
46+
- More detailed event segmentation
47+
- Better crash reporting integration
48+
- Real-time analytics dashboard
49+
- Advanced user analytics features
50+
51+
### Improved Performance
52+
53+
- Lightweight SDK with optimized network usage
54+
- Better error recovery mechanisms
55+
- Enhanced offline support
56+
57+
### Better Configuration Control
58+
59+
- Separate app key and server URL configuration
60+
- More granular control over analytics features
61+
- Better integration with crash reporting
62+
63+
## Configuration
64+
65+
The system uses environment variables for Countly configuration:
66+
67+
- `COUNTLY_APP_KEY`: Countly application key
68+
- `COUNTLY_SERVER_URL`: Countly server URL
69+
70+
When no app key is provided, the app runs without analytics entirely.
71+
72+
## Usage
73+
74+
The analytics interface remains exactly the same for backward compatibility:
75+
76+
### Using the Service Directly
77+
78+
```typescript
79+
import { countlyService } from '@/services/analytics.service';
80+
81+
// Track a simple event
82+
countlyService.trackEvent('user_login');
83+
84+
// Track an event with properties
85+
countlyService.trackEvent('button_clicked', {
86+
button_name: 'submit',
87+
screen: 'login',
88+
user_type: 'premium'
89+
});
90+
```
91+
92+
### Using the Hook (Recommended)
93+
94+
```typescript
95+
import { useAnalytics } from '@/hooks/use-analytics';
96+
97+
const { trackEvent } = useAnalytics();
98+
99+
// Track events
100+
trackEvent('screen_view', { screen_name: 'dashboard' });
101+
trackEvent('feature_used', { feature_name: 'gps_tracking' });
102+
```
103+
104+
## Testing
105+
106+
The implementation includes comprehensive unit tests:
107+
108+
### Countly Service Tests (`src/services/__tests__/countly.service.test.ts`)
109+
110+
- Event tracking functionality with Countly API
111+
- Error handling logic
112+
- Retry mechanism
113+
- Disable/enable functionality
114+
- Status tracking
115+
- Timer-based recovery
116+
- Property conversion to Countly segmentation format
117+
118+
### Countly Provider Tests (`src/components/common/__tests__/countly-provider.test.tsx`)
119+
120+
- Component rendering with Countly enabled/disabled
121+
- Error handling integration
122+
- Configuration validation
123+
- Service integration
124+
- Backward compatibility testing
125+
126+
### Analytics Hook Tests (`src/hooks/__tests__/use-analytics.test.ts`)
127+
128+
- Hook functionality
129+
- Service integration
130+
- Event tracking validation
131+
132+
All tests pass successfully and provide good coverage of the analytics functionality.
133+
134+
## Migration Notes
135+
136+
### Backward Compatibility
137+
138+
- All existing analytics calls work without changes
139+
- `AptabaseProviderWrapper` is still exported and functional
140+
- Service interface maintained identical to Aptabase version
141+
- Environment variables changed from `APTABASE_*` to `COUNTLY_*`
142+
143+
### Technical Changes
144+
145+
- Replaced `@aptabase/react-native` with `countly-sdk-react-native-bridge`
146+
- Updated service to convert properties to Countly segmentation format
147+
- Enhanced provider initialization with crash reporting support
148+
- Improved mock implementations for testing
149+
150+
### No Breaking Changes
151+
152+
- All application functionality remains intact
153+
- Analytics tracking continues to work seamlessly
154+
- Error handling patterns preserved
155+
- Performance characteristics maintained or improved
156+
157+
## Example Analytics Events
158+
159+
Here are some common analytics events with the new Countly implementation:
160+
161+
```typescript
162+
// User authentication
163+
countlyService.trackEvent('user_login', { method: 'email' });
164+
countlyService.trackEvent('user_logout');
165+
166+
// Navigation
167+
countlyService.trackEvent('screen_view', { screen_name: 'dashboard' });
168+
169+
// User actions
170+
countlyService.trackEvent('button_clicked', {
171+
button_name: 'emergency_call',
172+
screen: 'home'
173+
});
174+
175+
// Feature usage
176+
countlyService.trackEvent('feature_used', {
177+
feature_name: 'gps_tracking',
178+
enabled: true
179+
});
180+
181+
// Error tracking (in addition to Sentry)
182+
countlyService.trackEvent('error_occurred', {
183+
error_type: 'network',
184+
component: 'api_client'
185+
});
186+
```
187+
188+
## Best Practices
189+
190+
1. **Keep event names consistent**: Use snake_case for event names
191+
2. **Include relevant context**: Add properties that help understand user behavior
192+
3. **Don't track sensitive data**: Avoid PII or sensitive information
193+
4. **Use descriptive property names**: Make properties self-explanatory
194+
5. **Track both success and failure**: Include error states for complete picture
195+
6. **Leverage Countly's segmentation**: Use meaningful property values for better analytics
196+
197+
## Environment Setup
198+
199+
Add these variables to your environment configuration files (`.env.*`):
200+
201+
```bash
202+
# Replace your existing Aptabase configuration
203+
UNIT_COUNTLY_APP_KEY=your_countly_app_key_here
204+
UNIT_COUNTLY_SERVER_URL=https://your-countly-server.com
205+
```
206+
207+
The migration maintains full backward compatibility while providing enhanced analytics capabilities through Countly.

env.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,8 @@ const client = z.object({
9191
UNIT_MAPBOX_DLKEY: z.string(),
9292
IS_MOBILE_APP: z.boolean(),
9393
SENTRY_DSN: z.string(),
94-
APTABASE_URL: z.string(),
95-
APTABASE_APP_KEY: z.string(),
94+
COUNTLY_APP_KEY: z.string(),
95+
COUNTLY_SERVER_URL: z.string(),
9696
});
9797

9898
const buildTime = z.object({
@@ -125,8 +125,8 @@ const _clientEnv = {
125125
UNIT_MAPBOX_PUBKEY: process.env.UNIT_MAPBOX_PUBKEY || '',
126126
UNIT_MAPBOX_DLKEY: process.env.UNIT_MAPBOX_DLKEY || '',
127127
SENTRY_DSN: process.env.UNIT_SENTRY_DSN || '',
128-
APTABASE_APP_KEY: process.env.UNIT_APTABASE_APP_KEY || '',
129-
APTABASE_URL: process.env.UNIT_APTABASE_URL || '',
128+
COUNTLY_APP_KEY: process.env.UNIT_COUNTLY_APP_KEY || '',
129+
COUNTLY_SERVER_URL: process.env.UNIT_COUNTLY_SERVER_URL || '',
130130
};
131131

132132
/**

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@
4646
"e2e-test": "maestro test .maestro/ -e APP_ID=com.obytes.development"
4747
},
4848
"dependencies": {
49-
"@aptabase/react-native": "^0.3.10",
5049
"@config-plugins/react-native-callkeep": "^11.0.0",
5150
"@config-plugins/react-native-webrtc": "~12.0.0",
5251
"@dev-plugins/react-query": "~0.2.0",
@@ -101,6 +100,7 @@
101100
"axios": "~1.7.5",
102101
"babel-plugin-module-resolver": "^5.0.2",
103102
"buffer": "^6.0.3",
103+
"countly-sdk-react-native-bridge": "^25.4.0",
104104
"date-fns": "^4.1.0",
105105
"expo": "^53.0.0",
106106
"expo-application": "~6.1.5",

src/app/(app)/__tests__/calls.test.tsx

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ jest.mock('react-native', () => ({
1212
),
1313
RefreshControl: () => null,
1414
View: ({ children, ...props }: any) => <div {...props}>{children}</div>,
15+
StatusBar: {
16+
setBackgroundColor: jest.fn(),
17+
setTranslucent: jest.fn(),
18+
setHidden: jest.fn(),
19+
setBarStyle: jest.fn(),
20+
},
1521
}));
1622

1723
// Mock expo-router
@@ -170,12 +176,33 @@ jest.mock('lucide-react-native', () => ({
170176
X: () => <div></div>,
171177
}));
172178

173-
// Mock useFocusEffect
179+
// Mock navigation bar and color scheme
180+
jest.mock('expo-navigation-bar', () => ({
181+
setBackgroundColorAsync: jest.fn(() => Promise.resolve()),
182+
setBehaviorAsync: jest.fn(() => Promise.resolve()),
183+
setVisibilityAsync: jest.fn(() => Promise.resolve()),
184+
}));
185+
186+
jest.mock('nativewind', () => ({
187+
useColorScheme: jest.fn(() => ({ colorScheme: 'light' })),
188+
}));
189+
190+
jest.mock('react-native-edge-to-edge', () => ({
191+
SystemBars: ({ children, ...props }: any) => <div {...props}>{children}</div>,
192+
}));
193+
194+
// Mock FocusAwareStatusBar
195+
jest.mock('@/components/ui/focus-aware-status-bar', () => ({
196+
FocusAwareStatusBar: () => null,
197+
}));
198+
199+
// Mock useFocusEffect and useIsFocused
174200
jest.mock('@react-navigation/native', () => ({
175201
useFocusEffect: jest.fn((callback: () => void) => {
176202
const React = require('react');
177203
React.useEffect(callback, []);
178204
}),
205+
useIsFocused: jest.fn(() => true),
179206
}));
180207

181208
import CallsScreen from '../calls';

0 commit comments

Comments
 (0)