Skip to content

Commit

Permalink
Merge pull request #2283 from element-hq/feature/bma/logViewer
Browse files Browse the repository at this point in the history
View Folders and logs
  • Loading branch information
bmarty authored Jan 25, 2024
2 parents 99683d4 + 4eb10a2 commit 203262a
Show file tree
Hide file tree
Showing 59 changed files with 1,927 additions and 31 deletions.
1 change: 1 addition & 0 deletions appnav/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ dependencies {
implementation(libs.coil)

implementation(projects.features.ftue.api)
implementation(projects.features.viewfolder.api)

implementation(projects.services.apperror.impl)
implementation(projects.services.appnavstate.api)
Expand Down
26 changes: 26 additions & 0 deletions appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import io.element.android.features.login.api.oidc.OidcAction
import io.element.android.features.login.api.oidc.OidcActionFlow
import io.element.android.features.rageshake.api.bugreport.BugReportEntryPoint
import io.element.android.features.signedout.api.SignedOutEntryPoint
import io.element.android.features.viewfolder.api.ViewFolderEntryPoint
import io.element.android.libraries.architecture.BackstackView
import io.element.android.libraries.architecture.BaseFlowNode
import io.element.android.libraries.architecture.createNode
Expand All @@ -70,6 +71,7 @@ class RootFlowNode @AssistedInject constructor(
private val matrixClientsHolder: MatrixClientsHolder,
private val presenter: RootPresenter,
private val bugReportEntryPoint: BugReportEntryPoint,
private val viewFolderEntryPoint: ViewFolderEntryPoint,
private val signedOutEntryPoint: SignedOutEntryPoint,
private val intentResolver: IntentResolver,
private val oidcActionFlow: OidcActionFlow,
Expand Down Expand Up @@ -194,6 +196,11 @@ class RootFlowNode @AssistedInject constructor(

@Parcelize
data object BugReport : NavTarget

@Parcelize
data class ViewLogs(
val rootPath: String,
) : NavTarget
}

override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
Expand Down Expand Up @@ -233,12 +240,31 @@ class RootFlowNode @AssistedInject constructor(
override fun onBugReportSent() {
backstack.pop()
}

override fun onViewLogs(basePath: String) {
backstack.push(NavTarget.ViewLogs(rootPath = basePath))
}
}
bugReportEntryPoint
.nodeBuilder(this, buildContext)
.callback(callback)
.build()
}
is NavTarget.ViewLogs -> {
val callback = object : ViewFolderEntryPoint.Callback {
override fun onDone() {
backstack.pop()
}
}
val params = ViewFolderEntryPoint.Params(
rootPath = navTarget.rootPath,
)
viewFolderEntryPoint
.nodeBuilder(this, buildContext)
.params(params)
.callback(callback)
.build()
}
}
}

Expand Down
1 change: 1 addition & 0 deletions changelog.d/2276.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add in app logs viewer to the "Report a problem" screen.
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,6 @@ interface BugReportEntryPoint : FeatureEntryPoint {

interface Callback : Plugin {
fun onBugReportSent()
fun onViewLogs(basePath: String)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,9 @@ interface BugReporter {
* Set the current tracing filter.
*/
fun setCurrentTracingFilter(tracingFilter: String)

/**
* Save the logcat.
*/
fun saveLogCat()
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode
import io.element.android.features.rageshake.api.bugreport.BugReportEntryPoint
import io.element.android.features.rageshake.api.reporter.BugReporter
import io.element.android.libraries.androidutils.system.toast
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.ui.strings.CommonStrings
Expand All @@ -37,7 +38,12 @@ class BugReportNode @AssistedInject constructor(
@Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
private val presenter: BugReportPresenter,
private val bugReporter: BugReporter,
) : Node(buildContext, plugins = plugins) {
private fun onViewLogs(basePath: String) {
plugins<BugReportEntryPoint.Callback>().forEach { it.onViewLogs(basePath) }
}

@Composable
override fun View(modifier: Modifier) {
val state = presenter.present()
Expand All @@ -50,6 +56,11 @@ class BugReportNode @AssistedInject constructor(
activity?.toast(CommonStrings.common_report_submitted)
onDone()
},
onViewLogs = {
// Force a logcat dump
bugReporter.saveLogCat()
onViewLogs(bugReporter.logDirectory().absolutePath)
}
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,11 @@ import io.element.android.features.rageshake.impl.R
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.designsystem.components.async.AsyncActionView
import io.element.android.libraries.designsystem.components.form.textFieldState
import io.element.android.libraries.designsystem.components.preferences.PreferenceDivider
import io.element.android.libraries.designsystem.components.preferences.PreferencePage
import io.element.android.libraries.designsystem.components.preferences.PreferenceRow
import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch
import io.element.android.libraries.designsystem.components.preferences.PreferenceText
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.preview.debugPlaceholderBackground
Expand All @@ -55,6 +57,7 @@ import io.element.android.libraries.ui.strings.CommonStrings
@Composable
fun BugReportView(
state: BugReportState,
onViewLogs: () -> Unit,
onDone: () -> Unit,
onBackPressed: () -> Unit,
modifier: Modifier = Modifier,
Expand Down Expand Up @@ -97,6 +100,13 @@ fun BugReportView(
)
}
Spacer(modifier = Modifier.height(16.dp))
PreferenceDivider()
PreferenceText(
title = stringResource(id = R.string.screen_bug_report_view_logs),
enabled = isFormEnabled,
onClick = onViewLogs,
)
PreferenceDivider()
PreferenceSwitch(
isChecked = state.formState.sendLogs,
onCheckedChange = { eventSink(BugReportEvents.SetSendLog(it)) },
Expand Down Expand Up @@ -169,5 +179,6 @@ internal fun BugReportViewPreview(@PreviewParameter(BugReportStateProvider::clas
state = state,
onDone = {},
onBackPressed = {},
onViewLogs = {},
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ class DefaultBugReporter @Inject constructor(
private val logcatCommandDebug = arrayOf("logcat", "-d", "-v", "threadtime", "*:*")
private var currentTracingFilter: String? = null

private val logCatErrFile = File(logDirectory().absolutePath, LOG_CAT_FILENAME)

override suspend fun sendBugReport(
withDevicesLogs: Boolean,
withCrashLogs: Boolean,
Expand Down Expand Up @@ -130,8 +132,8 @@ class DefaultBugReporter @Inject constructor(
}

if (!isCancelled && (withCrashLogs || withDevicesLogs)) {
val gzippedLogcat = saveLogCat()

saveLogCat()
val gzippedLogcat = compressFile(logCatErrFile)
if (null != gzippedLogcat) {
if (gzippedFiles.size == 0) {
gzippedFiles.add(gzippedLogcat)
Expand Down Expand Up @@ -321,7 +323,9 @@ class DefaultBugReporter @Inject constructor(
}

override fun logDirectory(): File {
return File(context.cacheDir, LOG_DIRECTORY_NAME)
return File(context.cacheDir, LOG_DIRECTORY_NAME).apply {
mkdirs()
}
}

override fun cleanLogDirectoryIfNeeded() {
Expand Down Expand Up @@ -381,30 +385,19 @@ class DefaultBugReporter @Inject constructor(
*
* @return the file if the operation succeeds
*/
private fun saveLogCat(): File? {
val logCatErrFile = File(context.cacheDir.absolutePath, LOG_CAT_FILENAME)

override fun saveLogCat() {
if (logCatErrFile.exists()) {
logCatErrFile.safeDelete()
}

try {
logCatErrFile.writer().use {
getLogCatError(it)
}

return compressFile(logCatErrFile)
} catch (error: OutOfMemoryError) {
Timber.e(error, "## saveLogCat() : fail to write logcat OOM")
} catch (e: Exception) {
Timber.e(e, "## saveLogCat() : fail to write logcat")
} finally {
if (logCatErrFile.exists()) {
logCatErrFile.safeDelete()
}
}

return null
}

/**
Expand Down
1 change: 1 addition & 0 deletions features/rageshake/impl/src/main/res/values/localazy.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@
<string name="screen_bug_report_include_logs">"Allow logs"</string>
<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_view_logs">"View logs"</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>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ class FakeBugReporter(val mode: FakeBugReporterMode = FakeBugReporterMode.Succes
override fun setCurrentTracingFilter(tracingFilter: String) {
// No op
}

override fun saveLogCat() {
// No op
}
}

enum class FakeBugReporterMode {
Expand Down
26 changes: 26 additions & 0 deletions features/viewfolder/api/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
plugins {
id("io.element.android-library")
}

android {
namespace = "io.element.android.features.viewfolder.api"
}

dependencies {
implementation(projects.libraries.architecture)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.element.android.features.viewfolder.api

import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
import io.element.android.libraries.architecture.FeatureEntryPoint

interface ViewFolderEntryPoint : FeatureEntryPoint {
data class Params(
val rootPath: String,
)

fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder

interface NodeBuilder {
fun params(params: Params): NodeBuilder
fun callback(callback: Callback): NodeBuilder
fun build(): Node
}

interface Callback : Plugin {
fun onDone()
}
}
51 changes: 51 additions & 0 deletions features/viewfolder/impl/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

plugins {
id("io.element.android-compose-library")
alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
id("kotlin-parcelize")
}

android {
namespace = "io.element.android.features.viewfolder.impl"
}

anvil {
generateDaggerFactories.set(true)
}

dependencies {
implementation(projects.anvilannotations)
anvil(projects.anvilcodegen)
implementation(projects.libraries.androidutils)
implementation(projects.libraries.architecture)
implementation(projects.libraries.core)
implementation(projects.libraries.designsystem)
implementation(projects.libraries.uiStrings)
api(projects.features.viewfolder.api)
ksp(libs.showkase.processor)

testImplementation(libs.test.junit)
testImplementation(libs.test.robolectric)
testImplementation(libs.coroutines.test)
testImplementation(libs.molecule.runtime)
testImplementation(libs.test.truth)
testImplementation(libs.test.turbine)
testImplementation(projects.tests.testutils)
testImplementation(projects.libraries.matrix.test)
}
Loading

0 comments on commit 203262a

Please sign in to comment.