diff --git a/.github/workflows/kong.yml b/.github/workflows/kong.yml index fffdff9..53e5a96 100644 --- a/.github/workflows/kong.yml +++ b/.github/workflows/kong.yml @@ -9,8 +9,9 @@ on: env: test_api_key: ${{ secrets.KONG_SERVER_SDK_KEY }} - test_client_key: ${{ secrets. KONG_CLIENT_SDK_KEY }} - repo_pat: ${{ secrets.KONG_FINE_GRAINED_REPO_PAT }} + test_client_key: ${{ secrets.KONG_CLIENT_SDK_KEY }} + OAI_GITHUB_APP_ID: ${{ secrets.OAI_GITHUB_APP_ID }} + OAI_GITHUB_APP_PRIVATE_KEY: ${{ secrets.OAI_GITHUB_APP_PRIVATE_KEY }} FORCE_COLOR: true jobs: @@ -18,15 +19,28 @@ jobs: timeout-minutes: 10 runs-on: ubuntu-latest steps: + - uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3 + id: app-token + with: + client-id: ${{ secrets.OAI_GITHUB_APP_ID }} + private-key: ${{ secrets.OAI_GITHUB_APP_PRIVATE_KEY }} + owner: openai + repositories: statsig-priv-kong - name: Get KONG - run: git clone https://oauth2:$repo_pat@github.com/statsig-io/kong.git . + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + token: ${{ steps.app-token.outputs.token }} + repository: openai/statsig-priv-kong + ref: main + path: kong + persist-credentials: false - - uses: actions/setup-java@v4 + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: "17" distribution: "temurin" - - uses: actions/cache@v3 + - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: | ~/.gradle/caches @@ -37,16 +51,20 @@ jobs: ${{ runner.os }}-android-gradle- - name: Install Deps + working-directory: kong run: npm install - name: Setup Gradle - uses: gradle/actions/setup-gradle@v4 + uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 - name: Setup Android SDK + working-directory: kong run: npm run kong -- setup android -v - name: Precompile Android Bridge + working-directory: kong run: (cd bridges/android-bridge ; ./gradlew assemble) - name: Run Tests + working-directory: kong run: npm run kong -- test android -v -r diff --git a/.github/workflows/publish-to-maven.yml b/.github/workflows/publish-to-maven.yml index 2f91cc1..258a826 100644 --- a/.github/workflows/publish-to-maven.yml +++ b/.github/workflows/publish-to-maven.yml @@ -15,16 +15,16 @@ jobs: steps: - name: Checkout Code - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: distribution: 'temurin' java-version: '17' - name: Validate Gradle Wrapper - uses: gradle/actions/wrapper-validation@v5 + uses: gradle/actions/wrapper-validation@0723195856401067f7a2779048b490ace7a47d7c # v5 - name: Grant Execute Permission for Gradlew run: chmod +x gradlew diff --git a/.github/workflows/release-bot.yml b/.github/workflows/release-bot.yml index 71fa098..9f030a9 100644 --- a/.github/workflows/release-bot.yml +++ b/.github/workflows/release-bot.yml @@ -13,6 +13,14 @@ jobs: if: startsWith(github.head_ref, 'releases/') || github.event_name == 'release' runs-on: ubuntu-latest steps: - - uses: statsig-io/statsig-publish-sdk-action@main + - uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3 + id: app-token with: + client-id: ${{ secrets.OAI_GITHUB_APP_ID }} + private-key: ${{ secrets.OAI_GITHUB_APP_PRIVATE_KEY }} + owner: openai + repositories: statsig-publish-sdk-action + - uses: openai/statsig-publish-sdk-action@main + with: + token: ${{ steps.app-token.outputs.token }} kong-private-key: ${{ secrets.KONG_GH_APP_PRIVATE_KEY }} diff --git a/.github/workflows/scheduler.yml b/.github/workflows/scheduler.yml index 7e36350..3870097 100644 --- a/.github/workflows/scheduler.yml +++ b/.github/workflows/scheduler.yml @@ -12,7 +12,7 @@ jobs: steps: - name: Trigger Scheduled Test Runs if: github.event.repository.private - uses: actions/github-script@v6 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 with: script: | const args = { diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a79cbbd..dee38dc 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,17 +10,17 @@ on: jobs: gradle: timeout-minutes: 12 - runs-on: ubuntu-latest-16core + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - uses: actions/setup-java@v5 + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: 17 distribution: "adopt" - name: Setup Gradle - uses: gradle/actions/setup-gradle@v5 + uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 - name: Lint run: ./gradlew ktLintCheck @@ -30,7 +30,7 @@ jobs: - name: Upload test report on failure if: failure() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: test-report path: build/reports/tests/testDebugUnitTest/ diff --git a/gradle.properties b/gradle.properties index 9ba0438..40bcdf3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ android.useAndroidX=true -libraryVersion=5.0.0 +libraryVersion=5.1.0 org.gradle.caching=true org.gradle.configuration-cache=true org.gradle.configuration-cache.problems=warn diff --git a/src/main/java/com/statsig/androidsdk/StatsigClient.kt b/src/main/java/com/statsig/androidsdk/StatsigClient.kt index 660f6e4..783818c 100644 --- a/src/main/java/com/statsig/androidsdk/StatsigClient.kt +++ b/src/main/java/com/statsig/androidsdk/StatsigClient.kt @@ -117,6 +117,7 @@ class StatsigClient : LifecycleEventListener { @VisibleForTesting internal lateinit var options: StatsigOptions + private lateinit var runtimeMutableOptions: StatsigRuntimeMutableOptions private lateinit var connectivityListener: StatsigNetworkConnectivityListener @@ -675,7 +676,13 @@ class StatsigClient : LifecycleEventListener { enforceInitialized(functionName) errorBoundary.capture( { + this.runtimeMutableOptions = + StatsigRuntimeMutableOptions( + loggingEnabled = runtimeMutableOptions.loggingEnabled, + eventLoggingAPI = runtimeMutableOptions.eventLoggingAPI + ) this.logger.setLoggingEnabled(runtimeMutableOptions.loggingEnabled) + this.logger.setApi(runtimeMutableOptions.eventLoggingAPI) Log.v(TAG, "Runtime options successfully updated") }, tag = functionName @@ -1188,7 +1195,7 @@ class StatsigClient : LifecycleEventListener { retryScope.launch(dispatcherProvider.io) { try { this@StatsigClient.statsigNetwork.apiRetryFailedLogs( - this@StatsigClient.options.eventLoggingAPI, + this@StatsigClient.runtimeMutableOptions.eventLoggingAPI, this@StatsigClient.options.logEventFallbackUrls, statsigClientMetadata ) @@ -1267,6 +1274,11 @@ class StatsigClient : LifecycleEventListener { this.keyValueStorage = storage this.sdkKey = sdkKey this.options = options + this.runtimeMutableOptions = + StatsigRuntimeMutableOptions( + loggingEnabled = options.loggingEnabled, + eventLoggingAPI = options.eventLoggingAPI + ) val normalizedUser = normalizeUser(user) val initializeValues = options.initializeValues this.user = normalizedUser @@ -1313,14 +1325,14 @@ class StatsigClient : LifecycleEventListener { StatsigLogger( statsigScope, sdkKey, - options.eventLoggingAPI, + runtimeMutableOptions.eventLoggingAPI, statsigClientMetadata, statsigNetwork, normalizedUser, { store.getBootstrapMetadata() }, diagnostics, options.logEventFallbackUrls, - options.loggingEnabled, + runtimeMutableOptions.loggingEnabled, gson ) populateStatsigMetadata() @@ -1648,7 +1660,7 @@ class StatsigClient : LifecycleEventListener { } retryScope.launch(dispatcherProvider.io) { statsigNetwork.apiRetryFailedLogs( - this@StatsigClient.options.eventLoggingAPI, + this@StatsigClient.runtimeMutableOptions.eventLoggingAPI, this@StatsigClient.options.logEventFallbackUrls, statsigClientMetadata ) diff --git a/src/main/java/com/statsig/androidsdk/StatsigLogger.kt b/src/main/java/com/statsig/androidsdk/StatsigLogger.kt index 345d3fc..bc6226e 100644 --- a/src/main/java/com/statsig/androidsdk/StatsigLogger.kt +++ b/src/main/java/com/statsig/androidsdk/StatsigLogger.kt @@ -39,7 +39,7 @@ internal data class LogEventData( internal class StatsigLogger( private val coroutineScope: CoroutineScope, private val sdkKey: String, - private val api: String, + private var api: String, private val statsigMetadata: StatsigMetadata, private val statsigNetwork: StatsigNetwork, private val statsigUser: StatsigUser, @@ -259,6 +259,10 @@ internal class StatsigLogger( this.loggingEnabled = loggingEnabled } + fun setApi(api: String) { + this.api = api + } + private fun addManualFlag( metadata: MutableMap, isManual: Boolean diff --git a/src/main/java/com/statsig/androidsdk/StatsigOptions.kt b/src/main/java/com/statsig/androidsdk/StatsigOptions.kt index 35f0974..a706210 100644 --- a/src/main/java/com/statsig/androidsdk/StatsigOptions.kt +++ b/src/main/java/com/statsig/androidsdk/StatsigOptions.kt @@ -40,12 +40,11 @@ class StatsigOptions( @SerializedName("api") var api: String = DEFAULT_INIT_API, /** - * The endpoint to use for logging events. Default is "https://api.statsig.com/v1". - * The SDK will hit different endpoints for initialize + * The endpoint to use for logging events. The SDK will hit different endpoints for initialize * to evaluate gates and for logEvent to log event data. The api option controls the evaluation * endpoint, and eventLoggingApi controls the event logging endpoint. */ - @SerializedName("eventLoggingAPI") var eventLoggingAPI: String = DEFAULT_EVENT_API, + eventLoggingAPI: String = DEFAULT_EVENT_API, /** * The endpoint to use for logging sdk-internal exceptions for Statsig diagnostics. @@ -166,7 +165,7 @@ class StatsigOptions( var logNetworkMetadata: Boolean = false, loggingEnabled: Boolean = DEFAULT_LOGGING_ENABLED -) : StatsigRuntimeMutableOptions(loggingEnabled) { +) : StatsigRuntimeMutableOptions(loggingEnabled, eventLoggingAPI) { @SerializedName("autoValueUpdateIntervalMinutes") var autoValueUpdateIntervalMinutes = diff --git a/src/main/java/com/statsig/androidsdk/StatsigRuntimeMutableOptions.kt b/src/main/java/com/statsig/androidsdk/StatsigRuntimeMutableOptions.kt index 3a70a55..43f0e34 100644 --- a/src/main/java/com/statsig/androidsdk/StatsigRuntimeMutableOptions.kt +++ b/src/main/java/com/statsig/androidsdk/StatsigRuntimeMutableOptions.kt @@ -15,5 +15,12 @@ open class StatsigRuntimeMutableOptions( * [loggingEnabled] defaults to true if a value is not provided. */ @SerializedName("loggingEnabled") - var loggingEnabled: Boolean = DEFAULT_LOGGING_ENABLED + var loggingEnabled: Boolean = DEFAULT_LOGGING_ENABLED, + + /** + * The endpoint to use for logging events. This can be updated at runtime to support gradual + * cutovers between log ingestion endpoints. + */ + @SerializedName("eventLoggingAPI") + var eventLoggingAPI: String = DEFAULT_EVENT_API ) diff --git a/src/test/java/com/statsig/androidsdk/LogEventTest.kt b/src/test/java/com/statsig/androidsdk/LogEventTest.kt index 85c5eac..e6f6919 100644 --- a/src/test/java/com/statsig/androidsdk/LogEventTest.kt +++ b/src/test/java/com/statsig/androidsdk/LogEventTest.kt @@ -141,6 +141,33 @@ class LogEventTest { } } + @Test + fun testRuntimeMutableSettingsEventLoggingApi() = runBlocking { + val initialLogEventApi = "https://initial.fake.statsig.com/v1" + val updatedLogEventApi = "https://updated.fake.statsig.com/v1" + val options = StatsigOptions(eventLoggingAPI = initialLogEventApi) + + setup(options) + + Statsig.logEvent("event_before_api_update") + Statsig.flush() + + Statsig.updateRuntimeOptions( + StatsigRuntimeMutableOptions(eventLoggingAPI = updatedLogEventApi) + ) + + Statsig.logEvent("event_after_api_update") + Statsig.flush() + + coVerify(exactly = 1) { + network.apiPostLogs(initialLogEventApi, any(), any(), any(), any()) + } + coVerify(exactly = 1) { + network.apiPostLogs(updatedLogEventApi, any(), any(), any(), any()) + } + assertThat(options.eventLoggingAPI).isEqualTo(initialLogEventApi) + } + @Test fun testOverrideLoggingApi() = runBlocking { val apiPermutations = arrayOf(