Allow uploading notification push rules in bug reports (#5538)
* Allow uploading push rules in bug reports * Improve bug report screen previews * Update screenshots --------- Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
parent
35cf3aeb0b
commit
5b1bfac6ff
34 changed files with 116 additions and 38 deletions
|
|
@ -18,6 +18,7 @@ interface BugReporter {
|
|||
* @param withScreenshot true to include the screenshot
|
||||
* @param problemDescription the bug description
|
||||
* @param canContact true if the user opt in to be contacted directly
|
||||
* @param sendPushRules true to include the push rules
|
||||
* @param listener the listener
|
||||
*/
|
||||
suspend fun sendBugReport(
|
||||
|
|
@ -26,6 +27,7 @@ interface BugReporter {
|
|||
withScreenshot: Boolean,
|
||||
problemDescription: String,
|
||||
canContact: Boolean = false,
|
||||
sendPushRules: Boolean = false,
|
||||
listener: BugReporterListener
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -16,4 +16,5 @@ sealed interface BugReportEvents {
|
|||
data class SetSendLog(val sendLog: Boolean) : BugReportEvents
|
||||
data class SetCanContact(val canContact: Boolean) : BugReportEvents
|
||||
data class SetSendScreenshot(val sendScreenshot: Boolean) : BugReportEvents
|
||||
data class SetSendPushRules(val sendPushRules: Boolean) : BugReportEvents
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,6 +105,9 @@ class BugReportPresenter(
|
|||
is BugReportEvents.SetSendScreenshot -> updateFormState(formState) {
|
||||
copy(sendScreenshot = event.sendScreenshot)
|
||||
}
|
||||
is BugReportEvents.SetSendPushRules -> updateFormState(formState) {
|
||||
copy(sendPushRules = event.sendPushRules)
|
||||
}
|
||||
BugReportEvents.ClearError -> {
|
||||
sendingProgress.floatValue = 0f
|
||||
sendingAction.value = AsyncAction.Uninitialized
|
||||
|
|
@ -137,6 +140,7 @@ class BugReportPresenter(
|
|||
withScreenshot = formState.sendScreenshot,
|
||||
problemDescription = formState.description,
|
||||
canContact = formState.canContact,
|
||||
sendPushRules = formState.sendPushRules,
|
||||
listener = listener
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,14 +29,16 @@ data class BugReportFormState(
|
|||
val description: String,
|
||||
val sendLogs: Boolean,
|
||||
val canContact: Boolean,
|
||||
val sendScreenshot: Boolean
|
||||
val sendScreenshot: Boolean,
|
||||
val sendPushRules: Boolean,
|
||||
) : Parcelable {
|
||||
companion object {
|
||||
val Default = BugReportFormState(
|
||||
description = "",
|
||||
sendLogs = true,
|
||||
canContact = false,
|
||||
sendScreenshot = false
|
||||
sendScreenshot = false,
|
||||
sendPushRules = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
package io.element.android.features.rageshake.impl.bugreport
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
|
|
@ -26,6 +27,7 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coil3.compose.AsyncImage
|
||||
|
|
@ -40,7 +42,6 @@ import io.element.android.libraries.designsystem.components.preferences.Preferen
|
|||
import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch
|
||||
import io.element.android.libraries.designsystem.modifiers.onTabOrEnterKeyFocusNext
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.Button
|
||||
import io.element.android.libraries.designsystem.theme.components.ListItem
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
|
|
@ -142,6 +143,13 @@ fun BugReportView(
|
|||
}
|
||||
}
|
||||
}
|
||||
PreferenceSwitch(
|
||||
isChecked = state.formState.sendPushRules,
|
||||
onCheckedChange = { eventSink(BugReportEvents.SetSendPushRules(it)) },
|
||||
enabled = isFormEnabled,
|
||||
title = stringResource(R.string.screen_bug_report_send_notification_settings_title),
|
||||
subtitle = stringResource(R.string.screen_bug_report_send_notification_settings_description),
|
||||
)
|
||||
// Submit
|
||||
PreferenceRow {
|
||||
Button(
|
||||
|
|
@ -174,9 +182,20 @@ fun BugReportView(
|
|||
}
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Preview(heightDp = 1000)
|
||||
@Composable
|
||||
internal fun BugReportViewPreview(@PreviewParameter(BugReportStateProvider::class) state: BugReportState) = ElementPreview {
|
||||
internal fun BugReportViewDayPreview(@PreviewParameter(BugReportStateProvider::class) state: BugReportState) = ElementPreview {
|
||||
BugReportView(
|
||||
state = state,
|
||||
onSuccess = {},
|
||||
onBackClick = {},
|
||||
onViewLogs = {},
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(heightDp = 1000, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
internal fun BugReportViewNightPreview(@PreviewParameter(BugReportStateProvider::class) state: BugReportState) = ElementPreview {
|
||||
BugReportView(
|
||||
state = state,
|
||||
onSuccess = {},
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
|||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.asRequestBody
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okhttp3.Response
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
|
|
@ -113,6 +114,7 @@ class DefaultBugReporter(
|
|||
withScreenshot: Boolean,
|
||||
problemDescription: String,
|
||||
canContact: Boolean,
|
||||
sendPushRules: Boolean,
|
||||
listener: BugReporterListener,
|
||||
) {
|
||||
val url = bugReporterUrlProvider.provide().first()
|
||||
|
|
@ -181,6 +183,16 @@ class DefaultBugReporter(
|
|||
if (curveKey != null && edKey != null) {
|
||||
builder.addFormDataPart("device_keys", "curve25519:$curveKey, ed25519:$edKey")
|
||||
}
|
||||
|
||||
if (sendPushRules) {
|
||||
client.notificationSettingsService.getRawPushRules().getOrNull()?.let { pushRules ->
|
||||
builder.addFormDataPart(
|
||||
name = "file",
|
||||
filename = "push_rules.json",
|
||||
body = pushRules.toByteArray().toRequestBody(MimeTypes.Json.toMediaTypeOrNull())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (crashCallStack.isNotEmpty() && withCrashLogs) {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
<string name="screen_bug_report_include_screenshot">"Send screenshot"</string>
|
||||
<string name="screen_bug_report_logs_description">"Logs will be included with your message to make sure that everything is working properly. To send your message without logs, turn off this setting."</string>
|
||||
<string name="screen_bug_report_rash_logs_alert_title">"%1$s crashed the last time it was used. Would you like to share a crash report with us?"</string>
|
||||
<string name="screen_bug_report_send_notification_settings_description">"If you are having issues with notifications, uploading the notification settings can help us pinpoint the root cause."</string>
|
||||
<string name="screen_bug_report_send_notification_settings_description">"If you are having issues with notifications, uploading the notification push rules can help us pinpoint the root cause. Note these rules can contain private information, such as your display name or keywords to be notified for."</string>
|
||||
<string name="screen_bug_report_send_notification_settings_title">"Send notification settings"</string>
|
||||
<string name="screen_bug_report_view_logs">"View logs"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -106,6 +106,20 @@ class BugReportPresenterTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - send notification settings`() = runTest {
|
||||
val presenter = createPresenter()
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(BugReportEvents.SetSendPushRules(true))
|
||||
assertThat(awaitItem().formState).isEqualTo(BugReportFormState.Default.copy(sendPushRules = true))
|
||||
initialState.eventSink.invoke(BugReportEvents.SetSendPushRules(false))
|
||||
assertThat(awaitItem().formState).isEqualTo(BugReportFormState.Default.copy(sendPushRules = false))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - reset all`() = runTest {
|
||||
val presenter = createPresenter(
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ class FakeBugReporter(val mode: Mode = Mode.Success) : BugReporter {
|
|||
withScreenshot: Boolean,
|
||||
problemDescription: String,
|
||||
canContact: Boolean,
|
||||
sendPushRules: Boolean,
|
||||
listener: BugReporterListener,
|
||||
) {
|
||||
delay(100)
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import io.element.android.libraries.matrix.test.FakeSdkMetadata
|
|||
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
|
||||
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
|
||||
import io.element.android.libraries.matrix.test.tracing.FakeTracingService
|
||||
import io.element.android.libraries.network.useragent.DefaultUserAgentProvider
|
||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||
|
|
@ -65,6 +66,7 @@ class DefaultBugReporterTest {
|
|||
withDevicesLogs = true,
|
||||
withCrashLogs = true,
|
||||
withScreenshot = true,
|
||||
sendPushRules = true,
|
||||
problemDescription = "a bug occurred",
|
||||
canContact = true,
|
||||
listener = object : BugReporterListener {
|
||||
|
|
@ -109,7 +111,12 @@ class DefaultBugReporterTest {
|
|||
)
|
||||
|
||||
val fakeEncryptionService = FakeEncryptionService()
|
||||
val matrixClient = FakeMatrixClient(encryptionService = fakeEncryptionService)
|
||||
|
||||
val fakePushRules = "{ content: ... }"
|
||||
val fakeNotificationSettingsService = FakeNotificationSettingsService(
|
||||
getRawPushRulesResult = { Result.success(fakePushRules) }
|
||||
)
|
||||
val matrixClient = FakeMatrixClient(encryptionService = fakeEncryptionService, notificationSettingsService = fakeNotificationSettingsService)
|
||||
|
||||
fakeEncryptionService.givenDeviceKeys("CURVECURVECURVE", "EDKEYEDKEYEDKY")
|
||||
val sut = createDefaultBugReporter(
|
||||
|
|
@ -124,6 +131,7 @@ class DefaultBugReporterTest {
|
|||
withDevicesLogs = true,
|
||||
withCrashLogs = true,
|
||||
withScreenshot = true,
|
||||
sendPushRules = true,
|
||||
problemDescription = "a bug occurred",
|
||||
canContact = true,
|
||||
listener = object : BugReporterListener {
|
||||
|
|
@ -149,9 +157,11 @@ class DefaultBugReporterTest {
|
|||
assertThat(foundValues["user_id"]).isEqualTo("@foo:example.com")
|
||||
assertThat(foundValues["text"]).isEqualTo("a bug occurred")
|
||||
assertThat(foundValues["device_keys"]).isEqualTo("curve25519:CURVECURVECURVE, ed25519:EDKEYEDKEYEDKY")
|
||||
assertThat(foundValues["file"]).contains(fakePushRules)
|
||||
|
||||
// device_key now added given they are not null
|
||||
assertThat(progressValues.size).isEqualTo(EXPECTED_NUMBER_OF_PROGRESS_VALUE + 1)
|
||||
// so is the push_rules value
|
||||
assertThat(progressValues.size).isEqualTo(EXPECTED_NUMBER_OF_PROGRESS_VALUE + 2)
|
||||
|
||||
server.shutdown()
|
||||
}
|
||||
|
|
@ -272,6 +282,7 @@ class DefaultBugReporterTest {
|
|||
withDevicesLogs = true,
|
||||
withCrashLogs = true,
|
||||
withScreenshot = true,
|
||||
sendPushRules = true,
|
||||
problemDescription = "a bug occurred",
|
||||
canContact = true,
|
||||
listener = object : BugReporterListener {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import io.element.android.libraries.core.bool.orFalse
|
|||
@Suppress("ktlint:standard:property-naming")
|
||||
object MimeTypes {
|
||||
const val Any: String = "*/*"
|
||||
const val Json = "application/json"
|
||||
const val OctetStream = "application/octet-stream"
|
||||
const val Apk = "application/vnd.android.package-archive"
|
||||
const val Pdf = "application/pdf"
|
||||
|
|
|
|||
|
|
@ -33,4 +33,5 @@ interface NotificationSettingsService {
|
|||
suspend fun setInviteForMeEnabled(enabled: Boolean): Result<Unit>
|
||||
suspend fun getRoomsWithUserDefinedRules(): Result<List<String>>
|
||||
suspend fun canHomeServerPushEncryptedEventsToDevice(): Result<Boolean>
|
||||
suspend fun getRawPushRules(): Result<String?>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -139,4 +139,8 @@ class RustNotificationSettingsService(
|
|||
runCatchingExceptions {
|
||||
notificationSettings.await().canPushEncryptedEventToDevice()
|
||||
}
|
||||
|
||||
override suspend fun getRawPushRules(): Result<String?> = runCatchingExceptions {
|
||||
notificationSettings.await().getRawPushRules()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
|||
import io.element.android.libraries.matrix.api.room.RoomNotificationSettings
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_NOTIFICATION_MODE
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
|
||||
|
|
@ -23,6 +24,7 @@ class FakeNotificationSettingsService(
|
|||
initialEncryptedGroupDefaultMode: RoomNotificationMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY,
|
||||
initialOneToOneDefaultMode: RoomNotificationMode = RoomNotificationMode.ALL_MESSAGES,
|
||||
initialEncryptedOneToOneDefaultMode: RoomNotificationMode = RoomNotificationMode.ALL_MESSAGES,
|
||||
private val getRawPushRulesResult: () -> Result<String> = { lambdaError() },
|
||||
) : NotificationSettingsService {
|
||||
private val notificationSettingsStateFlow = MutableStateFlow(Unit)
|
||||
private var defaultGroupRoomNotificationMode: RoomNotificationMode = initialGroupDefaultMode
|
||||
|
|
@ -178,4 +180,8 @@ class FakeNotificationSettingsService(
|
|||
fun givenCanHomeServerPushEncryptedEventsToDeviceResult(result: Result<Boolean>) {
|
||||
canHomeServerPushEncryptedEventsToDeviceResult = result
|
||||
}
|
||||
|
||||
override suspend fun getRawPushRules(): Result<String?> {
|
||||
return getRawPushRulesResult()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3c229f0f963e247de755f45e55432191f88a51ba79baf49f0002c2c12dbdc09e
|
||||
size 48613
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:cbf1430985870c99c6e6a2b427257235219250e1c6805bde44de64d1382bd30a
|
||||
size 113544
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5bc84691892f956b6d282ae5aff0a7ef7900aad754bb7f04e25582c25a256342
|
||||
size 45892
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3c229f0f963e247de755f45e55432191f88a51ba79baf49f0002c2c12dbdc09e
|
||||
size 48613
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:26a5a053b88110ed76a59a99a199ada720fd7a44ff3b95546c4993ae3d8edddf
|
||||
size 37496
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c1020f77055e3369f012f73159be9e8c7bf582d4211b3b22f5409ecba46b6898
|
||||
size 47180
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:47c2e74119bd6f2329f15ee3b7dbf0115c69293bd34ed5c2e67de77319b7f820
|
||||
size 111436
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:984d43efa545fb9f104ea6d4eba87e768ade025eee8d1f74260049a64c7e7a7c
|
||||
size 44401
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c1020f77055e3369f012f73159be9e8c7bf582d4211b3b22f5409ecba46b6898
|
||||
size 47180
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:41c5eb671aaa7c8666942dbc808eb7b03f81cbde98906c5c17cdee61b1d9566b
|
||||
size 35446
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c56f65335dcc9cda18ed7f647bf1dc9428ee07762ea3d40e7d28731a81ca8f2e
|
||||
size 69552
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:12d9e6e297bbb3429a4fe6dd3ac16847280e479fe89e96ec78f35159a51d01f0
|
||||
size 108813
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9b9e42889c2b31dfee71a644b2da66c0b7cf4f4f475030d7aab3a7d500e7246a
|
||||
size 66366
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c56f65335dcc9cda18ed7f647bf1dc9428ee07762ea3d40e7d28731a81ca8f2e
|
||||
size 69552
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:57402ade9b70c2124b6764edbaac71d30b89c49eea8175c448cbe7520867a42f
|
||||
size 49569
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:38b5fb1099f10dae154270b5df1f52f4018d355d04e0040ba9c06ebe57f259c6
|
||||
size 67628
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1a5920a102082f35d0f0a7171687998f568da5e2d68a9f9d9b5884c55c832bd2
|
||||
size 106479
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5035c2ffa678e1c761030fbac56dfb3afd043c25a3df0cd6fb7c83b2b311c2ae
|
||||
size 63678
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:38b5fb1099f10dae154270b5df1f52f4018d355d04e0040ba9c06ebe57f259c6
|
||||
size 67628
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:946751ac246e3e833ec39da86b25d80766daed29449b1d0fcebe33c5079b724a
|
||||
size 46418
|
||||
Loading…
Add table
Add a link
Reference in a new issue