Skip to content
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

Use Firebase for analytics reporting #217

Merged
merged 18 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
8bb1bee
Add Capacitor plugin for Firebase analytics and do basic project setup
pkalita-lbl Jan 9, 2025
82b1f0d
Disable automatic screen_view tracking and manually log screen_view e…
pkalita-lbl Jan 9, 2025
bb12129
Replace @capacitor-community/firebase-analytics with @capacitor-fireb…
pkalita-lbl Jan 10, 2025
c5164be
Set and clear user ID for analytics events on login and logout
pkalita-lbl Jan 10, 2025
5d0794d
Use non-production mode for testing on device simulators
pkalita-lbl Jan 10, 2025
c738621
Use separate dev and prod iOS targets and configs
pkalita-lbl Jan 14, 2025
e5a6c50
Use separate dev and prod Android flavors and configs
pkalita-lbl Jan 14, 2025
17ddea4
Convert xcode environment folders to groups
pkalita-lbl Jan 14, 2025
3bb9521
Update the firebase config used by web versions
pkalita-lbl Jan 14, 2025
9f3ea28
Ensure firebase calls are no-ops without initialization
pkalita-lbl Jan 14, 2025
2c9d798
Update release process to take prod environment into account
pkalita-lbl Jan 14, 2025
3f885b2
Add analytics events for submission create, update, and delete
pkalita-lbl Jan 15, 2025
878c5a5
Add analytics events for sample create, update, and delete
pkalita-lbl Jan 15, 2025
ef8a05b
Add note about additional `adb` command require for using Android sim…
pkalita-lbl Jan 15, 2025
5ad0ef1
Add section on Firebase Analytics
pkalita-lbl Jan 15, 2025
4ced453
Use study nomenclature instead of submission
pkalita-lbl Jan 15, 2025
5b10f6d
Add docstring comments to sample event logging functions
pkalita-lbl Jan 15, 2025
9380f8d
Apply suggestions from code review
pkalita-lbl Jan 17, 2025
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
3 changes: 2 additions & 1 deletion .env.local.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
VITE_NMDC_SERVER_API_URL=http://127.0.0.1:8000
VITE_NMDC_SUBMISSION_SCHEMA_DOCS_BASE_URL=https://microbiomedata.github.io/submission-schema
VITE_NMDC_SUBMISSION_SCHEMA_DOCS_BASE_URL=https://microbiomedata.github.io/submission-schema
VITE_ENABLE_FIREBASE_ANALYTICS=false
1 change: 1 addition & 0 deletions .env.production
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
VITE_NMDC_SERVER_API_URL=https://data.microbiomedata.org
VITE_NMDC_SUBMISSION_SCHEMA_DOCS_BASE_URL=https://microbiomedata.github.io/submission-schema
VITE_ENABLE_FIREBASE_ANALYTICS=true
88 changes: 82 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,14 +179,42 @@ If either (a) this is your first time running the simulator, or (b) you have add
ionic capacitor sync ios
```

Start the simulator with live reloading enabled.
Start the simulator with live reloading enabled:

```shell
ionic cap run ios --livereload --external
npm run dev.ios
```

The simulator may take a few minutes to start. Eventually, the console will say "`Deploying App.app to <uuid>`" and the simulator will appear.

### (Optional) Run Android simulator

If you haven't already, download and install Android Studio and an Android SDK following the instructions in the [Capacitor documentation](https://capacitorjs.com/docs/getting-started/environment-setup#android-requirements).

If either (a) this is your first time running the simulator, or (b) you have added a Capacitor plugin since the last time you ran the simulator; run:

```shell
ionic capacitor sync android
```

Start the simulator with live reloading enabled:

```shell
npm run dev.android
```

Once the simulator is running and the app opens, you may see a message saying "The webpage at http://127.0.0.1:8100 could not be loaded because net::ERR_CONNECTION_REFUSED". In that case run the following while the simulator is running and then relaunch the app within the simulator:

```shell
adb reverse tcp:8100 tcp:8100
```

If you are using a [local](#using-a-local-nmdc-server) `nmdc-server` instance, you must forward the port to the Android simulator. Assuming your local `nmdc-server` is running on port 8000 (the default), run the following after the simulator has started:

```shell
adb reverse tcp:8000 tcp:8000
```

### Format code

We use [Prettier](https://prettier.io/) to ensure the files in this repository are formatted the way we want.
Expand Down Expand Up @@ -242,6 +270,52 @@ Here's how you can introduce a new environment variable to the code base:
4. Elsewhere in the code base, access the variable via the `config` object exported by `config.ts` (instead of
accessing `import.meta.env.{NAME}` directly)

### Debugging Firebase Analytics

#### Background

This project uses Firebase to collect analytics data. There are two separate Firebase projects: one for development and one for production. This is important so that analytics data generated by local development does not contribute to our actual metrics. If you need to access the Firebase console for either project, ask a team member to give you access.

Each Firebase project has its own native configuration files. Switching between the two projects is controlled by the `NODE_ENV` environment variable (see the iOS scheme/Android flavor settings in `capacitor.config.ts`). When the `NODE_ENV` is set to `production`, the app will use the production Firebase configuration files (`android/app/src/prod/google-services.json` and `ios/App/prod/GoogleService-Info.plist`). Otherwise, the app will use the development Firebase configuration files (`android/app/src/dev/google-services.json` and `ios/App/dev/GoogleService-Info.plist`).

> For more information on setting up multiple environments in Capacitor, see the [Capacitor documentation](https://capacitorjs.com/docs/guides/environment-specific-configurations). For Firebase-specific information, see the [Firebase documentation](https://firebase.google.com/docs/projects/multiprojects).

#### Enabling local analytics

**By default, local development instances will not send analytics data to Firebase**. This is controlled by the `VITE_ENABLE_FIREBASE_ANALYTICS` environment variable. You can enable local analytics by adding the following line to your `.env.local` file:

```
VITE_ENABLE_FIREBASE_ANALYTICS=true
```

Only add this line if you are actively developing code that sends analytics events, and be sure to remove it when you are done.

#### Debugging analytics events

It is recommended that you use a native device simulator to debug analytics events. This is because the native Firebase SDKs generate events which cannot be generated by the web SDK. For example to use an iOS simulator, run:

```shell
npm run dev.ios
```

The app's XCode project is already configured to use [Firebase's DebugView](https://firebase.google.com/docs/analytics/debugview) via the `-FIRDebugEnabled` flag. Once your device simulator is running, navigate to the DebugView page in the Firebase console for the "NMDC Field Notes DEV" project. You should see events being logged in real-time.

> Sometimes seems to take a few minutes and/or a page reload for the first events to appear.

If you use an Android simulator:

```shell
npm run dev.android
```

There is one extra step required to enable DebugView. Once the simulator is running, run the following command:

```shell
adb shell setprop debug.firebase.analytics.app org.microbiomedata.fieldnotes.dev
```

That must be run each time the Android simulator is started.

### Make a release

Creating a release involves three steps:
Expand Down Expand Up @@ -335,10 +409,12 @@ Creating a release involves three steps:
```shell
ionic capacitor open ios
```
2. "[Create an archive of the app](https://developer.apple.com/documentation/xcode/distributing-your-app-for-beta-testing-and-releases#Create-an-archive-of-your-app)" by clicking (in the toolbar) `Product` > `Archive`
3. "[Select the method of distribution](https://developer.apple.com/documentation/xcode/distributing-your-app-for-beta-testing-and-releases#Select-a-method-for-distribution)" to be TestFlight & App Store
4. Click the "Validate App" button to validate the build with respect to App Store Connect
5. Click the "Distribute App" button to upload the build to App Store Connect
2. Select the `App_Prod` scheme by clicking (in the toolbar) `Product` > `Scheme` > `App_Prod`.
3. "[Create an archive of the app](https://developer.apple.com/documentation/xcode/distributing-your-app-for-beta-testing-and-releases#Create-an-archive-of-your-app)" by clicking (in the toolbar) `Product` > `Archive`
4. "[Select the method of distribution](https://developer.apple.com/documentation/xcode/distributing-your-app-for-beta-testing-and-releases#Select-a-method-for-distribution)" to be TestFlight & App Store
5. Click the "Validate App" button to validate the build with respect to App Store Connect
6. Click the "Distribute App" button to upload the build to App Store Connect
7. Select the `App` scheme by clicking (in the toolbar) `Product` > `Scheme` > `App` (to return to the development scheme).
2. Distribute the iOS build via TestFlight:
1. Log in to [App Store Connect](https://appstoreconnect.apple.com/)
2. Go to `Apps` > `NMDC Field Notes` > `TestFlight`
Expand Down
18 changes: 14 additions & 4 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,19 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}

flavorDimensions = ["environment"]
productFlavors {
dev {
dimension "environment"
manifestPlaceholders = [displayName:"NMDC Field Notes DEV"]
applicationIdSuffix ".dev"
}
prod {
dimension "environment"
manifestPlaceholders = [displayName:"NMDC Field Notes"]
}
}
}

repositories {
Expand All @@ -45,10 +58,7 @@ dependencies {
apply from: 'capacitor.build.gradle'

try {
def servicesJSON = file('google-services.json')
if (servicesJSON.text) {
apply plugin: 'com.google.gms.google-services'
}
apply plugin: 'com.google.gms.google-services'
} catch(Exception e) {
logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work")
}
1 change: 1 addition & 0 deletions android/app/capacitor.build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ android {

apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies {
implementation project(':capacitor-firebase-analytics')
implementation project(':capacitor-app')
implementation project(':capacitor-browser')
implementation project(':capacitor-geolocation')
Expand Down
29 changes: 29 additions & 0 deletions android/app/src/dev/google-services.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"project_info": {
"project_number": "1064768027275",
"project_id": "nmdc-field-notes-dev",
"storage_bucket": "nmdc-field-notes-dev.firebasestorage.app"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:1064768027275:android:999dcb427e490b7f85b28e",
"android_client_info": {
"package_name": "org.microbiomedata.fieldnotes.dev"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyAjW-vz83y148Dp8cKsfCQK-G7vNRYloic"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "1"
}
27 changes: 9 additions & 18 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version='1.0' encoding='utf-8'?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:label="${displayName}"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
android:name=".MainActivity"
android:label="@string/title_activity_main"
android:theme="@style/AppTheme.NoActionBarLaunch"
android:exported="true"
android:label="${displayName}"
android:launchMode="singleTask"
android:exported="true">
android:name=".MainActivity"
android:theme="@style/AppTheme.NoActionBarLaunch">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
Expand All @@ -25,30 +25,21 @@
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="fieldnotes.microbiomedata.org" />
<data android:host="fieldnotes.microbiomedata.org" android:scheme="https" />
</intent-filter>

<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="@string/custom_url_scheme" />
</intent-filter>
</activity>

<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<provider android:authorities="${applicationId}.fileprovider" android:exported="false" android:grantUriPermissions="true" android:name="androidx.core.content.FileProvider">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" />
</provider>
<meta-data android:name="google_analytics_automatic_screen_reporting_enabled" android:value="false" />
</application>

<!-- Permissions -->

<uses-permission android:name="android.permission.INTERNET" />

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-feature android:name="android.hardware.location.gps" />
Expand Down
29 changes: 29 additions & 0 deletions android/app/src/prod/google-services.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"project_info": {
"project_number": "750735406507",
"project_id": "nmdc-field-notes-prod",
"storage_bucket": "nmdc-field-notes-prod.firebasestorage.app"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:750735406507:android:3141e5895de0b8d0aa8eee",
"android_client_info": {
"package_name": "org.microbiomedata.fieldnotes"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyCfjOg8PgAznjEscEVbbYUeVybVV1jXNK0"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "1"
}
3 changes: 3 additions & 0 deletions android/capacitor.settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
include ':capacitor-android'
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')

include ':capacitor-firebase-analytics'
project(':capacitor-firebase-analytics').projectDir = new File('../node_modules/@capacitor-firebase/analytics/android')

include ':capacitor-app'
project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/android')

Expand Down
44 changes: 42 additions & 2 deletions capacitor.config.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { CapacitorConfig } from "@capacitor/cli";

const config: CapacitorConfig = {
const baseConfig: CapacitorConfig = {
appId: "org.microbiomedata.fieldnotes",
appName: "NMDC Field Notes",
webDir: "dist",
server: {
androidScheme: "https",
cleartext: process.env.NODE_ENV !== "production",
},
plugins: {
SplashScreen: {
Expand All @@ -18,4 +17,45 @@ const config: CapacitorConfig = {
},
};

let config: CapacitorConfig;

switch (process.env.NODE_ENV) {
case "production":
case "prod":
// PRODUCTION CONFIGURATION
config = {
...baseConfig,
android: {
...baseConfig.android,
flavor: "prod",
},
ios: {
...baseConfig.ios,
scheme: "App_Prod",
},
};
break;

default:
// DEVELOPMENT CONFIGURATION
config = {
...baseConfig,
appId: baseConfig.appId + ".dev",
appName: baseConfig.appName + " DEV",
android: {
...baseConfig.android,
flavor: "dev",
},
ios: {
...baseConfig.ios,
scheme: "App",
},
server: {
...baseConfig.server,
cleartext: true,
},
};
break;
}

export default config;
Loading
Loading