Qa Release Build logs (TP-43188) (#8210)
Co-authored-by: Shivam Goyal <shivam.goyal@navi.com>
This commit is contained in:
@@ -29,6 +29,8 @@ import com.navi.analytics.alfred.utils.log
|
||||
import com.navi.analytics.utils.NaviTrackEvent
|
||||
import com.navi.base.sharedpref.PreferenceManager
|
||||
import com.navi.base.utils.AppLaunchUtils
|
||||
import com.navi.base.utils.QaReleaseLogUtil
|
||||
import com.navi.base.utils.QaReleaseLogUtil.buildQaReleaseLogMessage
|
||||
import com.navi.base.utils.isNotNullAndNotEmpty
|
||||
import com.navi.chat.base.ChatBaseActivity
|
||||
import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper
|
||||
@@ -102,6 +104,7 @@ open class NaviApplication : MultiDexApplication(), Application.ActivityLifecycl
|
||||
PreferenceManager.init(this)
|
||||
NaviSDKHelper.init(naviApplication = this)
|
||||
registerActivityLifecycleCallbacks(this)
|
||||
QaReleaseLogUtil.init(context = applicationContext, flavour = BuildConfig.FLAVOR)
|
||||
|
||||
// Dumping anr data to click stream
|
||||
ANRWatchDog().setIgnoreDebugger(true).setReportMainThreadOnly().setANRListener {
|
||||
@@ -123,6 +126,10 @@ open class NaviApplication : MultiDexApplication(), Application.ActivityLifecycl
|
||||
|
||||
anrEventProperties
|
||||
)
|
||||
buildQaReleaseLogMessage(
|
||||
data = anrEventProperties,
|
||||
logType = QaReleaseLogUtil.ReleaseLogType.ANR_LOG.name
|
||||
)
|
||||
if (isDifferentPackage.not() && (AlfredManager.config.getAlfredStatus() && AlfredManager.config.getAnrEnableStatus())) {
|
||||
anrEventProperties[STACK_TRACE] = it.cause?.stackTrace?.get(0).toString()
|
||||
val anrView =
|
||||
@@ -152,6 +159,10 @@ open class NaviApplication : MultiDexApplication(), Application.ActivityLifecycl
|
||||
NaviTrackEvent.trackEventOnClickStream(
|
||||
GLOBAL_APP_CRASH, crashEventProperties
|
||||
)
|
||||
buildQaReleaseLogMessage(
|
||||
data = crashEventProperties,
|
||||
logType = QaReleaseLogUtil.ReleaseLogType.CRASH_LOG.name
|
||||
)
|
||||
if (isDifferentPackage.not() && (AlfredManager.config.getAlfredStatus() && AlfredManager.config.getCrashEnableStatus())) {
|
||||
exception.stackTrace[0]?.let { stackTraceElement ->
|
||||
crashEventProperties[STACK_TRACE] = stackTraceElement.toString()
|
||||
|
||||
@@ -29,6 +29,7 @@ import com.navi.ap.utils.constants.APP_PLATFORM_APPLICATION_TYPE
|
||||
import com.navi.ap.utils.constants.PL
|
||||
import com.navi.base.deeplink.listener.DeepLinkListener
|
||||
import com.navi.base.deeplink.util.DeeplinkConstants.LOGOUT
|
||||
import com.navi.base.deeplink.util.DeeplinkConstants.RELEASE_LOG
|
||||
import com.navi.base.model.CtaData
|
||||
import com.navi.base.model.LineItem
|
||||
import com.navi.base.sharedpref.CommonPrefConstants.CURRENT_USER
|
||||
@@ -138,6 +139,7 @@ import com.naviapp.personalloanrevamp.getloanRevamp.activities.SkipMandateV2Acti
|
||||
import com.naviapp.personalloanrevamp.useridentificationv2.activities.PermissionV2Activity
|
||||
import com.naviapp.personalloanrevamp.useridentificationv2.activities.PermissionV2LocExpActivity
|
||||
import com.naviapp.registration.RegistrationActivity
|
||||
import com.naviapp.releaselog.activity.ReleaseLogActivity
|
||||
import com.naviapp.status_tracker.StatusTrackerActivity
|
||||
import com.naviapp.usernotification.activities.UserNotificationActivity
|
||||
import com.naviapp.utils.Constants
|
||||
@@ -902,6 +904,9 @@ object NaviDeepLinkNavigator : DeepLinkListener {
|
||||
LOGOUT -> {
|
||||
deleteCacheAndOpenLoginPage()
|
||||
}
|
||||
RELEASE_LOG -> {
|
||||
intent = Intent(activity, ReleaseLogActivity::class.java)
|
||||
}
|
||||
FORGE -> {
|
||||
intent = Intent(activity, ForgeActivity::class.java)
|
||||
}
|
||||
|
||||
@@ -20,11 +20,14 @@ import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.navi.analytics.utils.NaviTrackEvent
|
||||
import com.navi.base.deeplink.util.DeeplinkConstants.LOGOUT
|
||||
import com.navi.base.deeplink.util.DeeplinkConstants.RELEASE_LOG
|
||||
import com.navi.base.model.ActionData
|
||||
import com.navi.base.model.CtaData
|
||||
import com.navi.base.model.GenericAnalyticsData
|
||||
import com.navi.base.model.NaviClickAction
|
||||
import com.navi.base.model.NaviWidgetClickWithActionData
|
||||
import com.navi.base.sharedpref.PreferenceManager
|
||||
import com.navi.base.utils.QaReleaseLogUtil.isQaRelease
|
||||
import com.navi.base.utils.orTrue
|
||||
import com.navi.common.listeners.FragmentInterchangeListener
|
||||
import com.navi.common.ui.fragment.BaseFragment
|
||||
@@ -35,6 +38,7 @@ import com.navi.naviwidgets.models.WidgetChangedData
|
||||
import com.navi.naviwidgets.utils.APP_UPDATE_ENABLE
|
||||
import com.navi.naviwidgets.utils.IN_APP_UPDATE
|
||||
import com.navi.naviwidgets.viewholder.ViewHolderFactoryImpl
|
||||
import com.naviapp.BuildConfig
|
||||
import com.naviapp.R
|
||||
import com.naviapp.analytics.utils.NaviAnalytics
|
||||
import com.naviapp.analytics.utils.NaviAnalytics.Companion.PROFILE_BASIC_DETAILS
|
||||
@@ -49,6 +53,7 @@ import com.naviapp.home.analytics.LandingScreenAnalytics
|
||||
import com.naviapp.home.viewmodel.ProfileVM
|
||||
import com.naviapp.utils.IntentConstants
|
||||
import com.naviapp.utils.addDivider
|
||||
import com.naviapp.utils.setVisibilityState
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
@@ -96,6 +101,19 @@ class ProfileFragment : BaseFragment(), WidgetCallback {
|
||||
binding.backIcon.setOnClickListener {
|
||||
requireActivity().finish()
|
||||
}
|
||||
if (isQaRelease) {
|
||||
binding.btnLogs.apply {
|
||||
this.setVisibilityState(View.VISIBLE)
|
||||
this.setOnClickListener {
|
||||
NaviDeepLinkNavigator.navigate(
|
||||
activity = activity,
|
||||
ctaData = CtaData(url = RELEASE_LOG)
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
binding.btnLogs.setVisibilityState(View.GONE)
|
||||
}
|
||||
}
|
||||
|
||||
private fun reInitData() {
|
||||
|
||||
@@ -12,6 +12,8 @@ import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.navi.analytics.utils.NaviTrackEvent
|
||||
import com.navi.base.utils.BaseUtils
|
||||
import com.navi.base.utils.QaReleaseLogUtil
|
||||
import com.navi.base.utils.QaReleaseLogUtil.buildQaReleaseLogMessage
|
||||
import com.navi.common.CommonLibManager
|
||||
import com.navi.common.network.ApiConstants
|
||||
import com.navi.common.network.ApiConstants.API_CODE_ERROR
|
||||
@@ -59,7 +61,15 @@ abstract class ResponseCallback {
|
||||
}
|
||||
|
||||
private fun <T> handleResponse(response: Response<GenericResponse<T>>): RepoResult<T> {
|
||||
addApiUrlInErrorResponse(response?.body()?.errors, response?.raw()?.request?.url.toString())
|
||||
addApiUrlInErrorResponse(response.body()?.errors, response.raw().request.url.toString())
|
||||
buildQaReleaseLogMessage(
|
||||
responseData = response.body()?.data,
|
||||
statusCode = response.body()?.statusCode.toString(),
|
||||
request = response.raw().request,
|
||||
logType = QaReleaseLogUtil.ReleaseLogType.NETWORK_LOG.name,
|
||||
response = response.raw(),
|
||||
requestMethod = response.raw().request.method
|
||||
)
|
||||
response.body()?.let {
|
||||
if (it.errors?.firstOrNull()?.code == E_OFFER_EXPIRED) {
|
||||
handleError(it.errors, RedirectPageStatus(rejectReason = LOAN_OFFER_EXPIRED))
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.naviapp.releaselog.activity
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.navi.common.model.ModuleNameV2
|
||||
import com.navi.common.ui.activity.BaseActivity
|
||||
import com.naviapp.releaselog.screens.ReleaseLogScreen
|
||||
import com.naviapp.releaselog.viewmodel.ReleaseLogViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ReleaseLogActivity : BaseActivity() {
|
||||
|
||||
private val viewModel by viewModels<ReleaseLogViewModel>()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContent {
|
||||
ReleaseLogScreen(LocalContext.current, viewModel)
|
||||
}
|
||||
}
|
||||
|
||||
override val screenName: String get() = TAG
|
||||
override val moduleName: ModuleNameV2 get() = ModuleNameV2.COMMON
|
||||
|
||||
companion object {
|
||||
const val TAG = "RELEASE_LOG"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
package com.naviapp.releaselog.screens
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.LocalTextStyle
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.naviapp.dashboard.menu.customersupport.screens.NaviErrorScreen
|
||||
import com.naviapp.dashboard.menu.customersupport.screens.NaviLoadingScreen
|
||||
import com.naviapp.releaselog.viewmodel.ReleaseLogViewModel
|
||||
|
||||
|
||||
@Composable
|
||||
fun ReleaseLogScreen(context: Context, viewModel: ReleaseLogViewModel) {
|
||||
val logsDataState by viewModel.logsData.collectAsState()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.readLogFile(context = context)
|
||||
}
|
||||
|
||||
when (logsDataState) {
|
||||
ReleaseLogViewModel.ReleaseLogState.Error -> {
|
||||
NaviErrorScreen()
|
||||
}
|
||||
|
||||
ReleaseLogViewModel.ReleaseLogState.Loading -> {
|
||||
NaviLoadingScreen()
|
||||
}
|
||||
|
||||
is ReleaseLogViewModel.ReleaseLogState.Success -> {
|
||||
val data = (logsDataState as ReleaseLogViewModel.ReleaseLogState.Success).data
|
||||
ShowUi(data = data, context = context, viewModel = viewModel)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ShowUi(
|
||||
data: List<String>, context: Context, viewModel: ReleaseLogViewModel
|
||||
) {
|
||||
val clipboardManager =
|
||||
context.getSystemService(AppCompatActivity.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
var searchTextState by remember { mutableStateOf("") }
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
SearchBar(searchText = searchTextState,
|
||||
viewModel = viewModel,
|
||||
context = context,
|
||||
onSearchTextChanged = { searchQuery ->
|
||||
searchTextState = searchQuery
|
||||
if (searchQuery.isEmpty()) {
|
||||
viewModel.showOriginalLogs()
|
||||
}
|
||||
})
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
if (data.isNotEmpty()) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxHeight()
|
||||
) {
|
||||
data.forEach { log ->
|
||||
LogItem(log = log, onLongPress = {
|
||||
if (log.isNotEmpty()) {
|
||||
val clipData: ClipData = ClipData.newPlainText("Logs", log)
|
||||
clipboardManager.setPrimaryClip(clipData)
|
||||
Toast.makeText(
|
||||
context, "Copied to Clipboard", Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
} else {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(text = "No logs available", color = Color.Gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SearchBar(
|
||||
searchText: String,
|
||||
onSearchTextChanged: (String) -> Unit,
|
||||
viewModel: ReleaseLogViewModel,
|
||||
context: Context
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(Color.Gray)
|
||||
.padding(8.dp)
|
||||
.height(30.dp)
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.White),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
BasicTextField(
|
||||
value = searchText,
|
||||
onValueChange = {
|
||||
if (it != searchText) {
|
||||
onSearchTextChanged(it)
|
||||
}
|
||||
},
|
||||
textStyle = LocalTextStyle.current.copy(
|
||||
color = Color.Black, fontSize = 16.sp, letterSpacing = 0.15.sp
|
||||
),
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.background(Color.White)
|
||||
.padding(4.dp),
|
||||
singleLine = true,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
imeAction = ImeAction.Done
|
||||
),
|
||||
keyboardActions = KeyboardActions(onDone = {
|
||||
if (searchText.length < 3) {
|
||||
if (searchText.isEmpty()) {
|
||||
viewModel.showOriginalLogs()
|
||||
}
|
||||
Toast.makeText(context, "Min Three Characters", Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
viewModel.scrollToMatchedText(searchText = searchText)
|
||||
}
|
||||
})
|
||||
)
|
||||
SearchIcon(
|
||||
imageVector = Icons.Default.Search,
|
||||
contentDescription = "Search",
|
||||
onClick = {
|
||||
if (searchText.length < 3) {
|
||||
if (searchText.isEmpty()) {
|
||||
viewModel.showOriginalLogs()
|
||||
}
|
||||
Toast.makeText(context, "Min Three Characters", Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
viewModel.scrollToMatchedText(searchText = searchText)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun SearchIcon(
|
||||
imageVector: ImageVector, contentDescription: String?, onClick: () -> Unit
|
||||
) {
|
||||
val density = LocalDensity.current.density
|
||||
Image(imageVector = imageVector,
|
||||
contentDescription = contentDescription,
|
||||
modifier = Modifier
|
||||
.clickable { onClick() }
|
||||
.padding(8.dp)
|
||||
.size(8.dp * density))
|
||||
}
|
||||
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun LogItem(log: String, onLongPress: () -> Unit) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 8.dp, vertical = 10.dp)
|
||||
.combinedClickable(onClick = {}, onLongClick = onLongPress, onDoubleClick = {})
|
||||
) {
|
||||
Text(
|
||||
text = log, modifier = Modifier, maxLines = 1, overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.naviapp.releaselog.viewmodel
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.navi.base.utils.QA_RELEASE_LOGS_FILE_NAME
|
||||
import com.navi.base.utils.QA_RELEASE_LOGS_FOLDER_NAME
|
||||
import com.navi.common.utils.log
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.BufferedReader
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.InputStreamReader
|
||||
import com.navi.common.viewmodel.BaseVM
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class ReleaseLogViewModel @Inject constructor() : BaseVM() {
|
||||
|
||||
private val _logsData = MutableStateFlow<ReleaseLogState>(
|
||||
ReleaseLogState.Loading
|
||||
)
|
||||
val logsData = _logsData.asStateFlow()
|
||||
private var rawLogs: List<String> = emptyList()
|
||||
|
||||
fun readLogFile(context: Context) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
_logsData.emit(ReleaseLogState.Loading)
|
||||
val logDirectoryName = QA_RELEASE_LOGS_FOLDER_NAME
|
||||
val logFileName = QA_RELEASE_LOGS_FILE_NAME
|
||||
val logFile = File(context.filesDir, "$logDirectoryName/$logFileName")
|
||||
val logs = mutableListOf<String>()
|
||||
if (logFile.exists()) {
|
||||
val fileInputStream = FileInputStream(logFile)
|
||||
val bufferedReader = BufferedReader(InputStreamReader(fileInputStream))
|
||||
var line: String?
|
||||
val lines = mutableListOf<String>()
|
||||
val linesToReadFromBottom = 150
|
||||
|
||||
while (bufferedReader.readLine().also { line = it } != null) {
|
||||
line?.let {
|
||||
if (it.isNotEmpty()) {
|
||||
lines.add(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
val startIndex = maxOf(0, lines.size - linesToReadFromBottom)
|
||||
for (i in lines.size - 2 downTo startIndex) {
|
||||
logs.add(lines[i])
|
||||
}
|
||||
bufferedReader.close()
|
||||
fileInputStream.close()
|
||||
rawLogs = logs
|
||||
_logsData.emit(ReleaseLogState.Success(logs))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.log()
|
||||
_logsData.emit(ReleaseLogState.Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun showOriginalLogs() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
_logsData.emit(ReleaseLogState.Success(rawLogs))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun scrollToMatchedText(
|
||||
searchText: String
|
||||
) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
_logsData.emit(ReleaseLogState.Success(rawLogs.filter {
|
||||
it.contains(
|
||||
searchText, ignoreCase = true
|
||||
)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sealed class ReleaseLogState {
|
||||
object Loading : ReleaseLogState()
|
||||
data class Success(val data: List<String>) : ReleaseLogState()
|
||||
object Error : ReleaseLogState()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -37,6 +37,20 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<androidx.appcompat.widget.AppCompatButton
|
||||
android:id="@+id/btnLogs"
|
||||
style="@style/ActionButtonText6Style"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/_10dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:visibility="gone"
|
||||
android:text="@string/check_logs"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/my_toolbar" />
|
||||
|
||||
<com.facebook.shimmer.ShimmerFrameLayout
|
||||
android:id="@+id/shimmer_layout"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -1154,4 +1154,5 @@
|
||||
<string name="co_lending_partners">Co-lending partners</string>
|
||||
<string name="father_name">Father\'s name</string>
|
||||
<string name="father_name_hint">Enter full name</string>
|
||||
<string name="check_logs">Check Logs</string>
|
||||
</resources>
|
||||
|
||||
@@ -13,6 +13,15 @@
|
||||
<application
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
tools:ignore="MissingApplicationIcon"
|
||||
tools:targetApi="n" />
|
||||
tools:targetApi="n" >
|
||||
|
||||
<activity
|
||||
android:name=".releaselog.activity.ReleaseLogActivity"
|
||||
android:exported="false"
|
||||
android:screenOrientation="portrait"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:theme="@style/BaseThemeStyle" />
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -15,4 +15,5 @@ object DeeplinkConstants {
|
||||
const val SPLASH = "SPLASH"
|
||||
const val USER_DETAIL = "userdetail"
|
||||
const val LOGOUT = "logout"
|
||||
const val RELEASE_LOG = "RELEASE_LOG"
|
||||
}
|
||||
|
||||
@@ -33,3 +33,5 @@ const val SKIP_LOADER = "skipLoader"
|
||||
const val PAN_VERIFY_POLLING_FAIL="PAN_VERIFY_POLLING_FAIL"
|
||||
const val APPLICATION_JSON = "application/json"
|
||||
const val EXCLUDE_FROM_HASH_ENCRYPTION = "excludeFromHashEncryption"
|
||||
const val QA_RELEASE_LOGS_FILE_NAME = "NAVI_QA_RELEASE_LOGS.txt"
|
||||
const val QA_RELEASE_LOGS_FOLDER_NAME = "NAVI_QA_RELEASE_LOGS_FOLDER"
|
||||
175
navi-base/src/main/java/com/navi/base/utils/QaReleaseLogUtil.kt
Normal file
175
navi-base/src/main/java/com/navi/base/utils/QaReleaseLogUtil.kt
Normal file
@@ -0,0 +1,175 @@
|
||||
package com.navi.base.utils
|
||||
|
||||
import android.content.Context
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonParser
|
||||
import com.navi.base.BuildConfig
|
||||
import kotlinx.coroutines.asCoroutineDispatcher
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.OutputStreamWriter
|
||||
import java.net.URL
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
object QaReleaseLogUtil {
|
||||
|
||||
private lateinit var applicationContext: Context
|
||||
private val coroutineDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
|
||||
var isQaRelease: Boolean = false
|
||||
|
||||
fun init(context: Context, flavour: String) {
|
||||
applicationContext = context
|
||||
isQaRelease = flavour == QA && !BuildConfig.DEBUG
|
||||
}
|
||||
|
||||
fun buildQaReleaseLogMessage(
|
||||
statusCode: String? = null,
|
||||
responseData: Any? = null,
|
||||
logType: String,
|
||||
request: Request? = null,
|
||||
data: Map<String, String>? = null,
|
||||
response: Response? = null,
|
||||
requestMethod: String? = null
|
||||
) {
|
||||
if (!isQaRelease) {
|
||||
return
|
||||
}
|
||||
coroutineDispatcher.executor.execute {
|
||||
val logData = hashMapOf<String, Any>()
|
||||
val timestamp =
|
||||
SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(Date())
|
||||
.toString()
|
||||
var endPoint = ""
|
||||
when (logType) {
|
||||
ReleaseLogType.NETWORK_LOG.name -> {
|
||||
val networkData: HashMap<String, Any> = hashMapOf()
|
||||
endPoint = URL(request?.url.toString()).path
|
||||
|
||||
val responseJsonString = try {
|
||||
Gson().toJson(responseData)
|
||||
} catch (e: Exception) {
|
||||
""
|
||||
}
|
||||
val responseObject = try {
|
||||
JsonParser.parseString(responseJsonString).asJsonObject
|
||||
} catch (e: Exception) {
|
||||
JsonObject()
|
||||
}
|
||||
networkData["RESPONSE_BODY"] = responseObject
|
||||
networkData["RESPONSE_CODE"] = statusCode.toString()
|
||||
|
||||
val requestJsonString = try {
|
||||
Gson().toJson(responseData)
|
||||
} catch (e: Exception) {
|
||||
""
|
||||
}
|
||||
val requestObject = try {
|
||||
JsonParser.parseString(requestJsonString).asJsonObject
|
||||
} catch (e: Exception) {
|
||||
JsonObject()
|
||||
}
|
||||
networkData["REQUEST_BODY"] = requestObject
|
||||
networkData["URL"] = request?.url.toString()
|
||||
|
||||
val requestHeaderMap: HashMap<String, Any> = hashMapOf()
|
||||
request?.headers?.forEach {
|
||||
requestHeaderMap[it.first] = it.second
|
||||
}
|
||||
|
||||
val requestHeaderJsonString = try {
|
||||
Gson().toJson(requestHeaderMap)
|
||||
} catch (e: Exception) {
|
||||
EMPTY
|
||||
}
|
||||
val requestHeaderObject = try {
|
||||
JsonParser.parseString(requestHeaderJsonString).asJsonObject
|
||||
} catch (e: Exception) {
|
||||
JsonObject()
|
||||
}
|
||||
networkData["REQUEST_HEADER"] = requestHeaderObject
|
||||
|
||||
|
||||
val responseHeaderMap: HashMap<String, Any> = hashMapOf()
|
||||
response?.headers?.forEach {
|
||||
responseHeaderMap[it.first] = it.second
|
||||
}
|
||||
|
||||
val responseHeaderJsonString = try {
|
||||
Gson().toJson(responseHeaderMap)
|
||||
} catch (e: Exception) {
|
||||
EMPTY
|
||||
}
|
||||
val responseHeaderObject = try {
|
||||
JsonParser.parseString(responseHeaderJsonString).asJsonObject
|
||||
} catch (e: Exception) {
|
||||
JsonObject()
|
||||
}
|
||||
networkData["RESPONSE_HEADER"] = responseHeaderObject
|
||||
networkData["REQUEST_METHOD"] = requestMethod.toString()
|
||||
|
||||
logData[timestamp] = networkData
|
||||
}
|
||||
|
||||
ReleaseLogType.ANR_LOG.name, ReleaseLogType.CRASH_LOG.name -> {
|
||||
logData[timestamp] = data as HashMap<String, String>
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
val logMessage = Gson().toJson(logData)
|
||||
writeLogToFile(
|
||||
logMessage = logMessage, logType, timestamp, endPoint, statusCode, requestMethod
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun writeLogToFile(
|
||||
logMessage: String,
|
||||
logType: String,
|
||||
timeStamp: String,
|
||||
url: String,
|
||||
statusCode: String?,
|
||||
requestMethod: String?
|
||||
) {
|
||||
val logDirectoryName = QA_RELEASE_LOGS_FOLDER_NAME
|
||||
val logFileName = QA_RELEASE_LOGS_FILE_NAME
|
||||
val appInternalStorageDir = applicationContext.filesDir
|
||||
val logDirectory = File(appInternalStorageDir, logDirectoryName)
|
||||
if (!logDirectory.exists()) {
|
||||
logDirectory.mkdirs()
|
||||
}
|
||||
val logFile = File(logDirectory, logFileName)
|
||||
try {
|
||||
val fileOutputStream = FileOutputStream(logFile, true)
|
||||
val outputStreamWriter = OutputStreamWriter(fileOutputStream)
|
||||
|
||||
outputStreamWriter.append("\r\n")
|
||||
if (logType == ReleaseLogType.NETWORK_LOG.name) {
|
||||
outputStreamWriter.append("\r\n---$statusCode $requestMethod $url $timeStamp------")
|
||||
} else {
|
||||
outputStreamWriter.append("\r\n--- $logType $timeStamp------")
|
||||
}
|
||||
outputStreamWriter.append("\r\n")
|
||||
outputStreamWriter.append(logMessage)
|
||||
outputStreamWriter.flush()
|
||||
|
||||
outputStreamWriter.close()
|
||||
fileOutputStream.close()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
enum class ReleaseLogType {
|
||||
NETWORK_LOG,
|
||||
CRASH_LOG,
|
||||
ANR_LOG
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,10 +7,21 @@
|
||||
|
||||
package com.navi.pulse.network
|
||||
|
||||
import com.navi.base.utils.QaReleaseLogUtil
|
||||
import com.navi.base.utils.QaReleaseLogUtil.buildQaReleaseLogMessage
|
||||
import retrofit2.Response
|
||||
|
||||
class PulseNetworkRepository {
|
||||
suspend fun sendEvents(url: String, pulseRequest: PulseRequest): Response<PulseResponse> {
|
||||
return PulseRetrofitProvider.getApiService().sendEvents(url, pulseRequest)
|
||||
val response = PulseRetrofitProvider.getApiService().sendEvents(url, pulseRequest)
|
||||
buildQaReleaseLogMessage(
|
||||
responseData = response.body(),
|
||||
statusCode = response.body()?.code.toString(),
|
||||
request = response.raw().request,
|
||||
logType = QaReleaseLogUtil.ReleaseLogType.NETWORK_LOG.name,
|
||||
response = response.raw(),
|
||||
requestMethod = response.raw().request.method
|
||||
)
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user