gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-taler-android] 01/05: [wallet] WIP: observability events


From: gnunet
Subject: [taler-taler-android] 01/05: [wallet] WIP: observability events
Date: Mon, 01 Apr 2024 14:34:26 +0200

This is an automated email from the git hooks/post-receive script.

torsten-grote pushed a commit to branch master
in repository taler-android.

commit 4c78c29776fdd482e13fc445b68ad7fe091b4def
Author: Iván Ávalos <avalos@disroot.org>
AuthorDate: Wed Mar 27 12:08:29 2024 -0600

    [wallet] WIP: observability events
    
    bug 0008509
---
 .../main/java/net/taler/wallet/MainViewModel.kt    |  14 +-
 .../java/net/taler/wallet/backend/ApiResponse.kt   |   2 +
 .../java/net/taler/wallet/backend/InitResponse.kt  |  43 +++++
 .../net/taler/wallet/backend/WalletBackendApi.kt   |   8 +
 .../net/taler/wallet/events/ObservabilityDialog.kt | 129 +++++++++++++++
 .../net/taler/wallet/events/ObservabilityEvent.kt  | 181 +++++++++++++++++++++
 .../java/net/taler/wallet/exchanges/Exchanges.kt   |   6 +
 wallet/src/main/res/values/strings.xml             |  21 +++
 8 files changed, 403 insertions(+), 1 deletion(-)

diff --git a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt 
b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
index 5903446..cd1fbac 100644
--- a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
+++ b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
@@ -25,11 +25,13 @@ import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.viewModelScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
+import kotlinx.serialization.encodeToString
 import net.taler.common.Amount
 import net.taler.common.AmountParserException
 import net.taler.common.Event
 import net.taler.common.toEvent
 import net.taler.wallet.accounts.AccountManager
+import net.taler.wallet.backend.BackendManager
 import net.taler.wallet.backend.NotificationPayload
 import net.taler.wallet.backend.NotificationReceiver
 import net.taler.wallet.backend.VersionReceiver
@@ -38,6 +40,7 @@ import net.taler.wallet.backend.WalletCoreVersion
 import net.taler.wallet.balances.BalanceManager
 import net.taler.wallet.balances.ScopeInfo
 import net.taler.wallet.deposit.DepositManager
+import net.taler.wallet.events.ObservabilityEvent
 import net.taler.wallet.exchanges.ExchangeManager
 import net.taler.wallet.payment.PaymentManager
 import net.taler.wallet.peer.PeerManager
@@ -85,6 +88,9 @@ class MainViewModel(
     private val mTransactionsEvent = MutableLiveData<Event<ScopeInfo>>()
     val transactionsEvent: LiveData<Event<ScopeInfo>> = mTransactionsEvent
 
+    private val mObservabilityStream = 
MutableLiveData<Event<ObservabilityEvent>>()
+    val observabilityStream: LiveData<Event<ObservabilityEvent>> = 
mObservabilityStream
+
     private val mScanCodeEvent = MutableLiveData<Event<Boolean>>()
     val scanCodeEvent: LiveData<Event<Boolean>> = mScanCodeEvent
 
@@ -97,13 +103,19 @@ class MainViewModel(
 
     override fun onNotificationReceived(payload: NotificationPayload) {
         if (payload.type == "waiting-for-retry") return // ignore ping)
-        Log.i(TAG, "Received notification from wallet-core: $payload")
+
+        val str = BackendManager.json.encodeToString(payload)
+        Log.i(TAG, "Received notification from wallet-core: $str")
 
         // Only update balances when we're told they changed
         if (payload.type == "balance-change") 
viewModelScope.launch(Dispatchers.Main) {
             balanceManager.loadBalances()
         }
 
+        if (payload.type == "task-observability-event" && payload.event != 
null) {
+            mObservabilityStream.postValue(payload.event.toEvent())
+        }
+
         if (payload.type in transactionNotifications) 
viewModelScope.launch(Dispatchers.Main) {
             // TODO notification API should give us a currency to update
             // update currently selected transaction list
diff --git a/wallet/src/main/java/net/taler/wallet/backend/ApiResponse.kt 
b/wallet/src/main/java/net/taler/wallet/backend/ApiResponse.kt
index 46eb2f0..def4668 100644
--- a/wallet/src/main/java/net/taler/wallet/backend/ApiResponse.kt
+++ b/wallet/src/main/java/net/taler/wallet/backend/ApiResponse.kt
@@ -19,6 +19,7 @@ package net.taler.wallet.backend
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
 import kotlinx.serialization.json.JsonObject
+import net.taler.wallet.events.ObservabilityEvent
 
 @Serializable
 sealed class ApiMessage {
@@ -35,6 +36,7 @@ sealed class ApiMessage {
 data class NotificationPayload(
     val type: String,
     val id: String? = null,
+    val event: ObservabilityEvent? = null,
 )
 
 @Serializable
diff --git a/wallet/src/main/java/net/taler/wallet/backend/InitResponse.kt 
b/wallet/src/main/java/net/taler/wallet/backend/InitResponse.kt
index e9f7fcd..7fe1a6b 100644
--- a/wallet/src/main/java/net/taler/wallet/backend/InitResponse.kt
+++ b/wallet/src/main/java/net/taler/wallet/backend/InitResponse.kt
@@ -17,12 +17,55 @@
 package net.taler.wallet.backend
 
 import kotlinx.serialization.Serializable
+import net.taler.wallet.exchanges.BuiltinExchange
 
 @Serializable
 data class InitResponse(
     val versionInfo: WalletCoreVersion,
 )
 
+@Serializable
+data class WalletRunConfig(
+    val builtin: Builtin? = Builtin(),
+    val testing: Testing? = Testing(),
+    val features: Features? = Features(),
+) {
+    /**
+     * Initialization values useful for a complete startup.
+     *
+     * These are values may be overridden by different wallets
+     */
+    @Serializable
+    data class Builtin(
+        val exchanges: List<BuiltinExchange> = emptyList(),
+    )
+
+    /**
+     * Unsafe options which it should only be used to create
+     * testing environment.
+     */
+    @Serializable
+    data class Testing(
+        /**
+         * Allow withdrawal of denominations even though they are about to 
expire.
+         */
+        val denomselAllowLate: Boolean = false,
+        val devModeActive: Boolean = false,
+        val insecureTrustExchange: Boolean = false,
+        val preventThrottling: Boolean = false,
+        val skipDefaults: Boolean = false,
+        val emitObservabilityEvents: Boolean? = false,
+    )
+
+    /**
+     * Configurations values that may be safe to show to the user
+     */
+    @Serializable
+    data class Features(
+        val allowHttp: Boolean = false,
+    )
+}
+
 fun interface VersionReceiver {
     fun onVersionReceived(versionInfo: WalletCoreVersion)
 }
diff --git a/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt 
b/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt
index 4e179bb..0619a4e 100644
--- a/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt
+++ b/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt
@@ -23,11 +23,13 @@ import kotlinx.coroutines.GlobalScope
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 import kotlinx.serialization.KSerializer
+import kotlinx.serialization.encodeToString
 import kotlinx.serialization.json.JsonObject
 import kotlinx.serialization.json.decodeFromJsonElement
 import net.taler.wallet.backend.TalerErrorCode.NONE
 import org.json.JSONObject
 import java.io.File
+import net.taler.wallet.backend.WalletRunConfig.*
 
 private const val WALLET_DB = "talerwalletdb.sqlite3"
 
@@ -54,9 +56,15 @@ class WalletBackendApi(
         } else {
             "${app.filesDir}/${WALLET_DB}"
         }
+
+        val config = WalletRunConfig(testing = Testing(
+            emitObservabilityEvents = true,
+        ))
+
         request("init", InitResponse.serializer()) {
             put("persistentStoragePath", db)
             put("logLevel", "INFO")
+            put("config", 
JSONObject(BackendManager.json.encodeToString(config)))
         }.onSuccess { response ->
             versionReceiver.onVersionReceived(response.versionInfo)
         }.onError { error ->
diff --git 
a/wallet/src/main/java/net/taler/wallet/events/ObservabilityDialog.kt 
b/wallet/src/main/java/net/taler/wallet/events/ObservabilityDialog.kt
new file mode 100644
index 0000000..600f143
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/events/ObservabilityDialog.kt
@@ -0,0 +1,129 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2024 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under 
the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+ * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.wallet.events
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ContentCopy
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.ListItem
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontFamily
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.activityViewModels
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.getAndUpdate
+import kotlinx.serialization.encodeToString
+import net.taler.common.EventObserver
+import net.taler.wallet.MainViewModel
+import net.taler.wallet.R
+import net.taler.wallet.backend.BackendManager
+import net.taler.wallet.compose.copyToClipBoard
+
+class ObservabilityDialog: DialogFragment() {
+    private val model: MainViewModel by activityViewModels()
+    private val eventsFlow: MutableStateFlow<List<ObservabilityEvent>> = 
MutableStateFlow(emptyList())
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View = ComposeView(requireContext()).apply {
+        setContent {
+            val events by eventsFlow.collectAsState()
+            ObservabilityComposable(events = events) {
+                dismiss()
+            }
+        }
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        model.observabilityStream.observe(viewLifecycleOwner, EventObserver { 
event ->
+            eventsFlow.getAndUpdate {
+                it.toMutableList().apply {
+                    add(0, event)
+                }.toList()
+            }
+        })
+    }
+}
+
+@Composable
+fun ObservabilityComposable(
+    events: List<ObservabilityEvent>,
+    onDismiss: () -> Unit,
+) {
+    AlertDialog(
+        title = { Text(stringResource(R.string.observability_title)) },
+        text = {
+            LazyColumn(modifier = Modifier.fillMaxSize()) {
+                items(events) { event ->
+                    ObservabilityItem(event)
+                }
+            }
+        },
+        onDismissRequest = onDismiss,
+        confirmButton = {},
+        dismissButton = {
+            TextButton(onClick = onDismiss) {
+                Text(stringResource(R.string.close))
+            }
+        }
+    )
+}
+
+@Composable
+fun ObservabilityItem(event: ObservabilityEvent) {
+    val title = stringResource(event.titleRes)
+    val body = BackendManager.json.encodeToString(event)
+    val context = LocalContext.current
+
+    ListItem(
+        modifier = Modifier.fillMaxWidth(),
+        headlineContent = { Text(title) },
+        supportingContent = { Text(body, fontFamily = FontFamily.Monospace) },
+        trailingContent = {
+            IconButton(
+                content = { Icon(
+                    Icons.Default.ContentCopy,
+                    contentDescription = stringResource(R.string.copy),
+                ) },
+                onClick = {
+                    copyToClipBoard(context, "Event", body)
+                }
+            )
+        }
+    )
+}
\ No newline at end of file
diff --git a/wallet/src/main/java/net/taler/wallet/events/ObservabilityEvent.kt 
b/wallet/src/main/java/net/taler/wallet/events/ObservabilityEvent.kt
new file mode 100644
index 0000000..51e4f7a
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/events/ObservabilityEvent.kt
@@ -0,0 +1,181 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2024 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under 
the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+ * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.wallet.events
+
+import androidx.annotation.StringRes
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.json.JsonElement
+import net.taler.common.Timestamp
+import net.taler.wallet.R
+import net.taler.wallet.backend.TalerErrorInfo
+
+@Serializable
+sealed class ObservabilityEvent {
+    @get:StringRes
+    abstract val titleRes: Int
+
+    @Serializable
+    @SerialName("http-fetch-start")
+    data class HttpFetchStart(
+        val id: String,
+        @SerialName("when")
+        val timestamp: Timestamp,
+        val url: String,
+
+        override val titleRes: Int = R.string.event_http_fetch_start,
+    ): ObservabilityEvent()
+
+    @Serializable
+    @SerialName("http-fetch-finish-success")
+    data class HttpFetchFinishSuccess(
+        val id: String,
+        @SerialName("when")
+        val timestamp: Timestamp,
+        val url: String,
+        val status: Int,
+
+        override val titleRes: Int = R.string.event_http_fetch_finish_success,
+    ): ObservabilityEvent()
+
+    @Serializable
+    @SerialName("http-fetch-finish-error")
+    data class HttpFetchFinishError(
+        val id: String,
+        @SerialName("when")
+        val timestamp: Timestamp,
+        val url: String,
+        val error: TalerErrorInfo,
+
+        override val titleRes: Int = R.string.event_http_fetch_finish_error,
+    ): ObservabilityEvent()
+
+    @Serializable
+    @SerialName("db-query-start")
+    data class DbQueryStart(
+        val name: String,
+        val location: String,
+
+        override val titleRes: Int = R.string.event_db_query_start,
+    ): ObservabilityEvent()
+
+    @Serializable
+    @SerialName("db-query-finish-success")
+    data class DbQueryFinishSuccess(
+        val name: String,
+        val location: String,
+
+        override val titleRes: Int = R.string.event_db_query_finish_success,
+    ): ObservabilityEvent()
+
+    @Serializable
+    @SerialName("db-query-finish-error")
+    data class DbQueryFinishError(
+        val name: String,
+        val location: String,
+
+        override val titleRes: Int = R.string.event_db_query_finish_error,
+    ): ObservabilityEvent()
+
+    @Serializable
+    @SerialName("request-start")
+    data class RequestStart(
+        override val titleRes: Int = R.string.event_request_start,
+    ): ObservabilityEvent()
+
+    @Serializable
+    @SerialName("request-finish-success")
+    data class RequestFinishSuccess(
+        override val titleRes: Int = R.string.event_request_finish_success,
+    ): ObservabilityEvent()
+
+    @Serializable
+    @SerialName("request-finish-error")
+    data class RequestFinishError(
+        override val titleRes: Int = R.string.event_request_finish_error,
+    ): ObservabilityEvent()
+
+    @Serializable
+    @SerialName("task-start")
+    data class TaskStart(
+        val taskId: String,
+
+        override val titleRes: Int = R.string.event_task_start,
+    ): ObservabilityEvent()
+
+    @Serializable
+    @SerialName("task-stop")
+    data class TaskStop(
+        val taskId: String,
+
+        override val titleRes: Int = R.string.event_task_stop,
+    ): ObservabilityEvent()
+
+    @Serializable
+    @SerialName("task-reset")
+    data class TaskReset(
+        val taskId: String,
+
+        override val titleRes: Int = R.string.event_task_reset,
+    ): ObservabilityEvent()
+
+    @Serializable
+    @SerialName("declare-task-dependency")
+    data class DeclareTaskDependency(
+        val taskId: String,
+
+        override val titleRes: Int = R.string.event_declare_task_dependency,
+    ): ObservabilityEvent()
+
+    @Serializable
+    @SerialName("crypto-start")
+    data class CryptoStart(
+        val operation: String,
+
+        override val titleRes: Int = R.string.event_crypto_start,
+    ): ObservabilityEvent()
+
+    @Serializable
+    @SerialName("crypto-finish-success")
+    data class CryptoFinishSuccess(
+        val operation: String,
+
+        override val titleRes: Int = R.string.event_crypto_finished_success,
+    ): ObservabilityEvent()
+
+    @Serializable
+    @SerialName("crypto-finish-error")
+    data class CryptoFinishError(
+        val operation: String,
+
+        override val titleRes: Int = R.string.event_crypto_finished_error,
+    ): ObservabilityEvent()
+
+    @Serializable
+    @SerialName("sheperd-task-result")
+    data class ShepherdTaskResult(
+        val resultType: String,
+
+        override val titleRes: Int = R.string.event_shepherd_task_result,
+    ): ObservabilityEvent()
+
+    @Serializable
+    @SerialName("unknown")
+    data class Unknown(
+        override val titleRes: Int = R.string.event_unknown,
+    ): ObservabilityEvent()
+}
\ No newline at end of file
diff --git a/wallet/src/main/java/net/taler/wallet/exchanges/Exchanges.kt 
b/wallet/src/main/java/net/taler/wallet/exchanges/Exchanges.kt
index ce0bd82..0015e1c 100644
--- a/wallet/src/main/java/net/taler/wallet/exchanges/Exchanges.kt
+++ b/wallet/src/main/java/net/taler/wallet/exchanges/Exchanges.kt
@@ -20,6 +20,12 @@ import kotlinx.serialization.Serializable
 import net.taler.wallet.balances.ScopeInfo
 import net.taler.wallet.cleanExchange
 
+@Serializable
+data class BuiltinExchange(
+    val exchangeBaseUrl: String,
+    val currencyHint: String? = null,
+)
+
 @Serializable
 data class ExchangeItem(
     val exchangeBaseUrl: String,
diff --git a/wallet/src/main/res/values/strings.xml 
b/wallet/src/main/res/values/strings.xml
index 2ec3d40..db2630f 100644
--- a/wallet/src/main/res/values/strings.xml
+++ b/wallet/src/main/res/values/strings.xml
@@ -271,6 +271,27 @@ GNU Taler is immune against many types of fraud, such as 
phishing of credit card
     <string name="pending_operations_refuse">Refuse Proposal</string>
     <string name="pending_operations_no_action">(no action)</string>
 
+    <!-- Observability events -->
+    <string name="observability_title">Internal event log</string>
+    <string name="event_http_fetch_start">HTTP request started</string>
+    <string name="event_http_fetch_finish_success">HTTP request 
succeeded</string>
+    <string name="event_http_fetch_finish_error">HTTP request failed</string>
+    <string name="event_db_query_start">Database query started</string>
+    <string name="event_db_query_finish_success">Database query 
succeeded</string>
+    <string name="event_db_query_finish_error">Database query failed</string>
+    <string name="event_request_start">Request started</string>
+    <string name="event_request_finish_success">Request succeeded</string>
+    <string name="event_request_finish_error">Request failed</string>
+    <string name="event_task_start">Task started</string>
+    <string name="event_task_stop">Task stopped</string>
+    <string name="event_task_reset">Task reset</string>
+    <string name="event_declare_task_dependency">Task dependency 
declared</string>
+    <string name="event_crypto_start">Crypto operation started</string>
+    <string name="event_crypto_finished_success">Crypto operation 
succeeded</string>
+    <string name="event_crypto_finished_error">Crypto operation 
finished</string>
+    <string name="event_shepherd_task_result">Shepherd task result</string>
+    <string name="event_unknown">Unknown event</string>
+
     <string name="settings_dev_mode">Developer Mode</string>
     <string name="settings_dev_mode_summary">Shows more information intended 
for debugging</string>
     <string name="settings_withdraw_testkudos">Withdraw TESTKUDOS</string>

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]