Let enterprise build be able to override (or disable) the bug report URL.

This commit is contained in:
Benoit Marty
2025-08-08 15:17:00 +02:00
parent 76849c4374
commit d7e4e00b5d
40 changed files with 266 additions and 78 deletions

View File

@@ -8,15 +8,19 @@
package io.element.android.features.rageshake.impl
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.appconfig.RageshakeConfig
import io.element.android.appconfig.isEnabled
import io.element.android.features.rageshake.api.RageshakeFeatureAvailability
import io.element.android.features.rageshake.impl.reporter.BugReporterUrlProvider
import io.element.android.libraries.di.AppScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject
@ContributesBinding(AppScope::class)
class DefaultRageshakeFeatureAvailability @Inject constructor() : RageshakeFeatureAvailability {
override fun isAvailable(): Boolean {
return RageshakeConfig.isEnabled
class DefaultRageshakeFeatureAvailability @Inject constructor(
private val bugReporterUrlProvider: BugReporterUrlProvider,
) : RageshakeFeatureAvailability {
override fun isAvailable(): Flow<Boolean> {
return bugReporterUrlProvider.provide()
.map { it != null }
}
}

View File

@@ -5,10 +5,13 @@
* Please see LICENSE files in the repository root for full details.
*/
@file:OptIn(ExperimentalCoroutinesApi::class)
package io.element.android.features.rageshake.impl.crash
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import com.squareup.anvil.annotations.ContributesBinding
@@ -19,6 +22,8 @@ import io.element.android.features.rageshake.api.crash.CrashDetectionState
import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.di.AppScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.launch
import javax.inject.Inject
@@ -32,12 +37,15 @@ class DefaultCrashDetectionPresenter @Inject constructor(
@Composable
override fun present(): CrashDetectionState {
val localCoroutineScope = rememberCoroutineScope()
val crashDetected = remember {
if (rageshakeFeatureAvailability.isAvailable()) {
crashDataStore.appHasCrashed()
} else {
flowOf(false)
}
val crashDetected by remember {
rageshakeFeatureAvailability.isAvailable()
.flatMapLatest { isAvailable ->
if (isAvailable) {
crashDataStore.appHasCrashed()
} else {
flowOf(false)
}
}
}.collectAsState(false)
fun handleEvents(event: CrashDetectionEvents) {
@@ -49,7 +57,7 @@ class DefaultCrashDetectionPresenter @Inject constructor(
return CrashDetectionState(
appName = buildMeta.applicationName,
crashDetected = crashDetected.value,
crashDetected = crashDetected,
eventSink = ::handleEvents
)
}

View File

@@ -39,7 +39,7 @@ class DefaultRageshakePreferencesPresenter @Inject constructor(
val isSupported: MutableState<Boolean> = rememberSaveable {
mutableStateOf(rageshake.isAvailable())
}
val isFeatureAvailable = remember { rageshakeFeatureAvailability.isAvailable() }
val isFeatureAvailable by remember { rageshakeFeatureAvailability.isAvailable() }.collectAsState(false)
val isEnabled by remember {
rageshakeDataStore.isEnabled()
}.collectAsState(initial = false)

View File

@@ -0,0 +1,22 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.rageshake.impl.reporter
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.appconfig.RageshakeConfig
import io.element.android.libraries.di.AppScope
import javax.inject.Inject
fun interface BugReportAppNameProvider {
fun provide(): String
}
@ContributesBinding(AppScope::class)
class DefaultBugReportAppNameProvider @Inject constructor() : BugReportAppNameProvider {
override fun provide(): String = RageshakeConfig.BUG_REPORT_APP_NAME
}

View File

@@ -7,8 +7,9 @@
package io.element.android.features.rageshake.impl.reporter
import kotlinx.coroutines.flow.Flow
import okhttp3.HttpUrl
fun interface BugReporterUrlProvider {
fun provide(): HttpUrl
fun provide(): Flow<HttpUrl?>
}

View File

@@ -114,6 +114,12 @@ class DefaultBugReporter @Inject constructor(
canContact: Boolean,
listener: BugReporterListener,
) {
val url = bugReporterUrlProvider.provide().first()
if (url == null) {
// It should not happen, but if the URL is null, we cannot proceed
Timber.e("## sendBugReport() : bug report URL is null")
error("Bug report URL is null, cannot send bug report")
}
// enumerate files to delete
val bugReportFiles: MutableList<File> = ArrayList()
var response: Response? = null
@@ -243,7 +249,7 @@ class DefaultBugReporter @Inject constructor(
}
// build the request
val request = Request.Builder()
.url(bugReporterUrlProvider.provide())
.url(url)
.post(requestBody)
.build()
var errorMessage: String? = null

View File

@@ -9,14 +9,31 @@ package io.element.android.features.rageshake.impl.reporter
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.appconfig.RageshakeConfig
import io.element.android.features.enterprise.api.BugReportUrl
import io.element.android.features.enterprise.api.EnterpriseService
import io.element.android.libraries.di.AppScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import javax.inject.Inject
@ContributesBinding(AppScope::class)
class DefaultBugReporterUrlProvider @Inject constructor() : BugReporterUrlProvider {
override fun provide(): HttpUrl {
return RageshakeConfig.BUG_REPORT_URL.toHttpUrl()
class DefaultBugReporterUrlProvider @Inject constructor(
private val bugReportAppNameProvider: BugReportAppNameProvider,
private val enterpriseService: EnterpriseService,
) : BugReporterUrlProvider {
override fun provide(): Flow<HttpUrl?> {
if (bugReportAppNameProvider.provide().isEmpty()) return flowOf(null)
return enterpriseService.bugReportUrlFlow
.map { bugReportUrl ->
when (bugReportUrl) {
is BugReportUrl.Custom -> bugReportUrl.url
BugReportUrl.Disabled -> null
BugReportUrl.UseDefault -> RageshakeConfig.BUG_REPORT_URL.takeIf { it.isNotEmpty() }
}
}
.map { it?.toHttpUrl() }
}
}