-
Notifications
You must be signed in to change notification settings - Fork 4
CU-868ffcgaz Countly implementation #170
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| /** | ||
| * Mock for Countly React Native SDK | ||
| * Used during testing to prevent actual analytics calls | ||
| */ | ||
|
|
||
| const mockCountly = { | ||
| init: jest.fn().mockResolvedValue(undefined), | ||
| start: jest.fn().mockResolvedValue(undefined), | ||
| enableCrashReporting: jest.fn().mockResolvedValue(undefined), | ||
| events: { | ||
| recordEvent: jest.fn().mockResolvedValue(undefined), | ||
| }, | ||
| }; | ||
|
|
||
| export default mockCountly; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,207 @@ | ||||||||||||||||||
| # Analytics Migration: Aptabase to Countly | ||||||||||||||||||
|
|
||||||||||||||||||
| ## Migration Completed | ||||||||||||||||||
|
|
||||||||||||||||||
| This project has been successfully migrated from Aptabase to Countly for analytics tracking while maintaining full backward compatibility. | ||||||||||||||||||
|
|
||||||||||||||||||
| ## Previous Aptabase Implementation | ||||||||||||||||||
|
|
||||||||||||||||||
| Previously, the application used Aptabase for analytics with comprehensive error handling: | ||||||||||||||||||
|
|
||||||||||||||||||
| - **Aptabase Service**: Centralized error handling and retry logic | ||||||||||||||||||
| - **Aptabase Provider Wrapper**: Error-safe provider with fallback rendering | ||||||||||||||||||
| - **Simple Configuration**: Single app key configuration | ||||||||||||||||||
|
|
||||||||||||||||||
| ## Current Countly Implementation | ||||||||||||||||||
|
|
||||||||||||||||||
| ### 1. Countly Service (`src/services/analytics.service.ts`) | ||||||||||||||||||
|
|
||||||||||||||||||
| - **Purpose**: Centralized analytics tracking with error handling | ||||||||||||||||||
| - **Features**: | ||||||||||||||||||
| - Simple event tracking interface compatible with previous Aptabase interface | ||||||||||||||||||
| - Graceful error handling with retry logic | ||||||||||||||||||
| - Automatic service disable/enable after failures | ||||||||||||||||||
| - Comprehensive logging of events and errors | ||||||||||||||||||
| - Converts event properties to Countly segmentation format | ||||||||||||||||||
|
|
||||||||||||||||||
| ### 2. Countly Provider Wrapper (`src/components/common/aptabase-provider.tsx`) | ||||||||||||||||||
|
|
||||||||||||||||||
| - **Purpose**: Initializes Countly SDK with error handling | ||||||||||||||||||
| - **Features**: | ||||||||||||||||||
| - Safe initialization with error recovery | ||||||||||||||||||
| - Uses the Countly service for error management | ||||||||||||||||||
| - Always renders children (no provider wrapper required) | ||||||||||||||||||
| - Configurable with app key and server URL | ||||||||||||||||||
| - Backward compatible `AptabaseProviderWrapper` export | ||||||||||||||||||
|
|
||||||||||||||||||
| ### 3. Updated Layout (`src/app/_layout.tsx`) | ||||||||||||||||||
|
|
||||||||||||||||||
| - **Purpose**: Uses the new Countly wrapper with updated configuration | ||||||||||||||||||
| - **Change**: Updated to pass both `COUNTLY_APP_KEY` and `COUNTLY_SERVER_URL` | ||||||||||||||||||
|
|
||||||||||||||||||
| ## Key Benefits of Migration | ||||||||||||||||||
|
|
||||||||||||||||||
| ### Enhanced Analytics Capabilities | ||||||||||||||||||
|
|
||||||||||||||||||
| - More detailed event segmentation | ||||||||||||||||||
| - Better crash reporting integration | ||||||||||||||||||
| - Real-time analytics dashboard | ||||||||||||||||||
| - Advanced user analytics features | ||||||||||||||||||
|
|
||||||||||||||||||
| ### Improved Performance | ||||||||||||||||||
|
|
||||||||||||||||||
| - Lightweight SDK with optimized network usage | ||||||||||||||||||
| - Better error recovery mechanisms | ||||||||||||||||||
| - Enhanced offline support | ||||||||||||||||||
|
|
||||||||||||||||||
| ### Better Configuration Control | ||||||||||||||||||
|
|
||||||||||||||||||
| - Separate app key and server URL configuration | ||||||||||||||||||
| - More granular control over analytics features | ||||||||||||||||||
| - Better integration with crash reporting | ||||||||||||||||||
|
|
||||||||||||||||||
| ## Configuration | ||||||||||||||||||
|
|
||||||||||||||||||
| The system uses environment variables for Countly configuration: | ||||||||||||||||||
|
|
||||||||||||||||||
| - `COUNTLY_APP_KEY`: Countly application key | ||||||||||||||||||
| - `COUNTLY_SERVER_URL`: Countly server URL | ||||||||||||||||||
|
|
||||||||||||||||||
| When no app key is provided, the app runs without analytics entirely. | ||||||||||||||||||
|
|
||||||||||||||||||
| ## Usage | ||||||||||||||||||
|
|
||||||||||||||||||
| The analytics interface remains exactly the same for backward compatibility: | ||||||||||||||||||
|
|
||||||||||||||||||
| ### Using the Service Directly | ||||||||||||||||||
|
|
||||||||||||||||||
| ```typescript | ||||||||||||||||||
| import { countlyService } from '@/services/analytics.service'; | ||||||||||||||||||
|
|
||||||||||||||||||
| // Track a simple event | ||||||||||||||||||
| countlyService.trackEvent('user_login'); | ||||||||||||||||||
|
|
||||||||||||||||||
| // Track an event with properties | ||||||||||||||||||
| countlyService.trackEvent('button_clicked', { | ||||||||||||||||||
| button_name: 'submit', | ||||||||||||||||||
| screen: 'login', | ||||||||||||||||||
| user_type: 'premium' | ||||||||||||||||||
| }); | ||||||||||||||||||
| ``` | ||||||||||||||||||
|
|
||||||||||||||||||
| ### Using the Hook (Recommended) | ||||||||||||||||||
|
|
||||||||||||||||||
| ```typescript | ||||||||||||||||||
| import { useAnalytics } from '@/hooks/use-analytics'; | ||||||||||||||||||
|
|
||||||||||||||||||
| const { trackEvent } = useAnalytics(); | ||||||||||||||||||
|
|
||||||||||||||||||
| // Track events | ||||||||||||||||||
| trackEvent('screen_view', { screen_name: 'dashboard' }); | ||||||||||||||||||
| trackEvent('feature_used', { feature_name: 'gps_tracking' }); | ||||||||||||||||||
| ``` | ||||||||||||||||||
|
|
||||||||||||||||||
| ## Testing | ||||||||||||||||||
|
|
||||||||||||||||||
| The implementation includes comprehensive unit tests: | ||||||||||||||||||
|
|
||||||||||||||||||
| ### Countly Service Tests (`src/services/__tests__/countly.service.test.ts`) | ||||||||||||||||||
|
|
||||||||||||||||||
| - Event tracking functionality with Countly API | ||||||||||||||||||
| - Error handling logic | ||||||||||||||||||
| - Retry mechanism | ||||||||||||||||||
| - Disable/enable functionality | ||||||||||||||||||
| - Status tracking | ||||||||||||||||||
| - Timer-based recovery | ||||||||||||||||||
| - Property conversion to Countly segmentation format | ||||||||||||||||||
|
|
||||||||||||||||||
| ### Countly Provider Tests (`src/components/common/__tests__/countly-provider.test.tsx`) | ||||||||||||||||||
|
|
||||||||||||||||||
| - Component rendering with Countly enabled/disabled | ||||||||||||||||||
| - Error handling integration | ||||||||||||||||||
| - Configuration validation | ||||||||||||||||||
| - Service integration | ||||||||||||||||||
| - Backward compatibility testing | ||||||||||||||||||
|
|
||||||||||||||||||
| ### Analytics Hook Tests (`src/hooks/__tests__/use-analytics.test.ts`) | ||||||||||||||||||
|
|
||||||||||||||||||
| - Hook functionality | ||||||||||||||||||
| - Service integration | ||||||||||||||||||
| - Event tracking validation | ||||||||||||||||||
|
|
||||||||||||||||||
| All tests pass successfully and provide good coverage of the analytics functionality. | ||||||||||||||||||
|
|
||||||||||||||||||
| ## Migration Notes | ||||||||||||||||||
|
|
||||||||||||||||||
| ### Backward Compatibility | ||||||||||||||||||
|
|
||||||||||||||||||
| - All existing analytics calls work without changes | ||||||||||||||||||
| - `AptabaseProviderWrapper` is still exported and functional | ||||||||||||||||||
| - Service interface maintained identical to Aptabase version | ||||||||||||||||||
| - Environment variables changed from `APTABASE_*` to `COUNTLY_*` | ||||||||||||||||||
|
|
||||||||||||||||||
| ### Technical Changes | ||||||||||||||||||
|
|
||||||||||||||||||
| - Replaced `@aptabase/react-native` with `countly-sdk-react-native-bridge` | ||||||||||||||||||
| - Updated service to convert properties to Countly segmentation format | ||||||||||||||||||
| - Enhanced provider initialization with crash reporting support | ||||||||||||||||||
| - Improved mock implementations for testing | ||||||||||||||||||
|
|
||||||||||||||||||
|
Comment on lines
+145
to
+149
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Align “Technical Changes” with actual initialization approach Reflect modern initWithConfig usage rather than Countly.init/start in examples. -- Enhanced provider initialization with crash reporting support
+- Enhanced provider initialization using CountlyConfig.initWithConfig with crash reporting enabled📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||
| ### No Breaking Changes | ||||||||||||||||||
|
|
||||||||||||||||||
| - All application functionality remains intact | ||||||||||||||||||
| - Analytics tracking continues to work seamlessly | ||||||||||||||||||
| - Error handling patterns preserved | ||||||||||||||||||
| - Performance characteristics maintained or improved | ||||||||||||||||||
|
|
||||||||||||||||||
| ## Example Analytics Events | ||||||||||||||||||
|
|
||||||||||||||||||
| Here are some common analytics events with the new Countly implementation: | ||||||||||||||||||
|
|
||||||||||||||||||
| ```typescript | ||||||||||||||||||
| // User authentication | ||||||||||||||||||
| countlyService.trackEvent('user_login', { method: 'email' }); | ||||||||||||||||||
| countlyService.trackEvent('user_logout'); | ||||||||||||||||||
|
|
||||||||||||||||||
| // Navigation | ||||||||||||||||||
| countlyService.trackEvent('screen_view', { screen_name: 'dashboard' }); | ||||||||||||||||||
|
|
||||||||||||||||||
| // User actions | ||||||||||||||||||
| countlyService.trackEvent('button_clicked', { | ||||||||||||||||||
| button_name: 'emergency_call', | ||||||||||||||||||
| screen: 'home' | ||||||||||||||||||
| }); | ||||||||||||||||||
|
|
||||||||||||||||||
| // Feature usage | ||||||||||||||||||
| countlyService.trackEvent('feature_used', { | ||||||||||||||||||
| feature_name: 'gps_tracking', | ||||||||||||||||||
| enabled: true | ||||||||||||||||||
| }); | ||||||||||||||||||
|
|
||||||||||||||||||
| // Error tracking (in addition to Sentry) | ||||||||||||||||||
| countlyService.trackEvent('error_occurred', { | ||||||||||||||||||
| error_type: 'network', | ||||||||||||||||||
| component: 'api_client' | ||||||||||||||||||
| }); | ||||||||||||||||||
| ``` | ||||||||||||||||||
|
|
||||||||||||||||||
| ## Best Practices | ||||||||||||||||||
|
|
||||||||||||||||||
| 1. **Keep event names consistent**: Use snake_case for event names | ||||||||||||||||||
| 2. **Include relevant context**: Add properties that help understand user behavior | ||||||||||||||||||
| 3. **Don't track sensitive data**: Avoid PII or sensitive information | ||||||||||||||||||
| 4. **Use descriptive property names**: Make properties self-explanatory | ||||||||||||||||||
| 5. **Track both success and failure**: Include error states for complete picture | ||||||||||||||||||
| 6. **Leverage Countly's segmentation**: Use meaningful property values for better analytics | ||||||||||||||||||
|
|
||||||||||||||||||
| ## Environment Setup | ||||||||||||||||||
|
|
||||||||||||||||||
| Add these variables to your environment configuration files (`.env.*`): | ||||||||||||||||||
|
|
||||||||||||||||||
| ```bash | ||||||||||||||||||
| # Replace your existing Aptabase configuration | ||||||||||||||||||
| UNIT_COUNTLY_APP_KEY=your_countly_app_key_here | ||||||||||||||||||
| UNIT_COUNTLY_SERVER_URL=https://your-countly-server.com | ||||||||||||||||||
| ``` | ||||||||||||||||||
|
|
||||||||||||||||||
| The migration maintains full backward compatibility while providing enhanced analytics capabilities through Countly. | ||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -12,6 +12,12 @@ jest.mock('react-native', () => ({ | |||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||
| RefreshControl: () => null, | ||||||||||||||||||||||||||||||||||||||
| View: ({ children, ...props }: any) => <div {...props}>{children}</div>, | ||||||||||||||||||||||||||||||||||||||
| StatusBar: { | ||||||||||||||||||||||||||||||||||||||
| setBackgroundColor: jest.fn(), | ||||||||||||||||||||||||||||||||||||||
| setTranslucent: jest.fn(), | ||||||||||||||||||||||||||||||||||||||
| setHidden: jest.fn(), | ||||||||||||||||||||||||||||||||||||||
| setBarStyle: jest.fn(), | ||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // Mock expo-router | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -170,12 +176,33 @@ jest.mock('lucide-react-native', () => ({ | |||||||||||||||||||||||||||||||||||||
| X: () => <div>✕</div>, | ||||||||||||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // Mock useFocusEffect | ||||||||||||||||||||||||||||||||||||||
| // Mock navigation bar and color scheme | ||||||||||||||||||||||||||||||||||||||
| jest.mock('expo-navigation-bar', () => ({ | ||||||||||||||||||||||||||||||||||||||
| setBackgroundColorAsync: jest.fn(() => Promise.resolve()), | ||||||||||||||||||||||||||||||||||||||
| setBehaviorAsync: jest.fn(() => Promise.resolve()), | ||||||||||||||||||||||||||||||||||||||
| setVisibilityAsync: jest.fn(() => Promise.resolve()), | ||||||||||||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| jest.mock('nativewind', () => ({ | ||||||||||||||||||||||||||||||||||||||
| useColorScheme: jest.fn(() => ({ colorScheme: 'light' })), | ||||||||||||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| jest.mock('react-native-edge-to-edge', () => ({ | ||||||||||||||||||||||||||||||||||||||
| SystemBars: ({ children, ...props }: any) => <div {...props}>{children}</div>, | ||||||||||||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // Mock FocusAwareStatusBar | ||||||||||||||||||||||||||||||||||||||
| jest.mock('@/components/ui/focus-aware-status-bar', () => ({ | ||||||||||||||||||||||||||||||||||||||
| FocusAwareStatusBar: () => null, | ||||||||||||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // Mock useFocusEffect and useIsFocused | ||||||||||||||||||||||||||||||||||||||
| jest.mock('@react-navigation/native', () => ({ | ||||||||||||||||||||||||||||||||||||||
| useFocusEffect: jest.fn((callback: () => void) => { | ||||||||||||||||||||||||||||||||||||||
| const React = require('react'); | ||||||||||||||||||||||||||||||||||||||
| React.useEffect(callback, []); | ||||||||||||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||||||||||||
| useIsFocused: jest.fn(() => true), | ||||||||||||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
200
to
206
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Preserve other @react-navigation/native exports and wire cleanup correctly. Exporting only two hooks can break consumers; also model cleanup via useEffect wrapper. -jest.mock('@react-navigation/native', () => ({
- useFocusEffect: jest.fn((callback: () => void) => {
- const React = require('react');
- React.useEffect(callback, []);
- }),
- useIsFocused: jest.fn(() => true),
-}));
+jest.mock('@react-navigation/native', () => {
+ const actual = jest.requireActual('@react-navigation/native');
+ const { useEffect } = require('react');
+ return {
+ ...actual,
+ useFocusEffect: jest.fn((callback: () => void | (() => void)) => {
+ useEffect(() => callback(), []);
+ }),
+ useIsFocused: jest.fn(() => true),
+ };
+});📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| import CallsScreen from '../calls'; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix incorrect file references and init API in docs
Docs point to aptabase-provider.tsx and show Countly.init/start, but code uses countly-provider.tsx with CountlyConfig + initWithConfig. Correct to prevent developer confusion.
📝 Committable suggestion
🤖 Prompt for AI Agents