Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.simprints.feature.dashboard.settings.troubleshooting
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.simprints.feature.dashboard.settings.troubleshooting.events.EventScopeLogFragment
import com.simprints.feature.dashboard.settings.troubleshooting.overview.OverviewFragment
import com.simprints.infra.uibase.annotations.ExcludedFromGeneratedTestCoverageReports

Expand All @@ -20,10 +21,7 @@ internal class TroubleshootingPagerAdapter(
val factory: () -> Fragment,
) {

// TODO Replace stub fragments with proper ones
Overview("Overview", { OverviewFragment() }),
Events("Events", { StubFragment() }),
Workers("Worker log", { StubFragment() }),
;
Events("Event scopes", { EventScopeLogFragment() }),
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.simprints.feature.dashboard.settings.troubleshooting.adapter

data class TroubleshootingItemViewData(
val title: String,
val subtitle: String = "",
val body: String = "",
val navigationId: String? = null,
Comment thread
alexandr-simprints marked this conversation as resolved.
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.simprints.feature.dashboard.settings.troubleshooting.adapter

import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import com.simprints.core.ExcludedFromGeneratedTestCoverageReports
import com.simprints.feature.dashboard.databinding.ItemTroubleshootingListBinding
import com.simprints.infra.uibase.system.Clipboard


@ExcludedFromGeneratedTestCoverageReports("UI classes are not unit tested")
internal class TroubleshootingListAdapter(
private val items: List<TroubleshootingItemViewData>,
private val onMoreClick: (String) -> Unit = {},
) : RecyclerView.Adapter<TroubleshootingListAdapter.ViewHolder>() {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ItemTroubleshootingListBinding.inflate(layoutInflater, parent, false)
return ViewHolder(binding)
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(items[position])
}

override fun getItemCount(): Int = items.size

@ExcludedFromGeneratedTestCoverageReports("UI classes are not unit tested")
inner class ViewHolder(
private val binding: ItemTroubleshootingListBinding
) : RecyclerView.ViewHolder(binding.root) {

fun bind(item: TroubleshootingItemViewData) {
binding.troubleshootingItemTitle.text = item.title
binding.troubleshootingItemSubtitle.text = item.subtitle
binding.troubleshootingItemBody.text = item.body

binding.troubleshootingItemButton.isVisible = item.navigationId != null
binding.troubleshootingItemButton.setOnClickListener {
item.navigationId?.let(onMoreClick)
}

binding.troubleshootingItemCopy.setOnClickListener {
val context = binding.root.context
Clipboard.copyToClipboard(context, "${item.title}\n${item.subtitle}\n${item.body}")
Toast.makeText(context, "Copied to clipboard", Toast.LENGTH_SHORT).show()
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.simprints.feature.dashboard.settings.troubleshooting.events

import android.os.Bundle
import android.view.View
import androidx.core.view.isGone
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import com.simprints.feature.dashboard.R
import com.simprints.feature.dashboard.databinding.FragmentTroubleshootingStandaloneListBinding
import com.simprints.feature.dashboard.settings.troubleshooting.adapter.TroubleshootingListAdapter
import com.simprints.infra.uibase.viewbinding.viewBinding
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
internal class EventLogFragment : Fragment(R.layout.fragment_troubleshooting_standalone_list) {

private val args by navArgs<EventLogFragmentArgs>()

private val viewModel by viewModels<EventsLogViewModel>()
private val binding by viewBinding(FragmentTroubleshootingStandaloneListBinding::bind)

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

binding.troubleshootingToolbar.title = args.scopeId
binding.troubleshootingToolbar.setNavigationOnClickListener {
findNavController().navigateUp()
}

viewModel.events.observe(viewLifecycleOwner) {
binding.troubleshootingListProgress.isGone = it.isNotEmpty()
binding.troubleshootingList.adapter = TroubleshootingListAdapter(it)
}
viewModel.collectEvents(args.scopeId)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.simprints.feature.dashboard.settings.troubleshooting.events

import android.os.Bundle
import android.view.View
import androidx.core.view.isGone
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import com.simprints.feature.dashboard.R
import com.simprints.feature.dashboard.databinding.FragmentTroubleshootingListBinding
import com.simprints.feature.dashboard.settings.troubleshooting.TroubleshootingFragmentDirections
import com.simprints.feature.dashboard.settings.troubleshooting.adapter.TroubleshootingListAdapter
import com.simprints.infra.uibase.viewbinding.viewBinding
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
internal class EventScopeLogFragment : Fragment(R.layout.fragment_troubleshooting_list) {

private val viewModel by viewModels<EventsLogViewModel>()
private val binding by viewBinding(FragmentTroubleshootingListBinding::bind)

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

viewModel.scopes.observe(viewLifecycleOwner) {
binding.troubleshootingListProgress.isGone = it.isNotEmpty()
binding.troubleshootingList.adapter =
TroubleshootingListAdapter(it) { openEventsList(it) }
}
viewModel.collectEventScopes()
}

private fun openEventsList(scopeId: String) {
findNavController().navigate(
TroubleshootingFragmentDirections
.actionTroubleshootingFragmentToTroubleshootingEventLogFragment(scopeId)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.simprints.feature.dashboard.settings.troubleshooting.events

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.simprints.feature.dashboard.settings.troubleshooting.adapter.TroubleshootingItemViewData
import com.simprints.infra.events.EventRepository
import com.simprints.infra.events.event.domain.models.Event
import com.simprints.infra.events.event.domain.models.scope.EventScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import java.util.Date
import javax.inject.Inject

@HiltViewModel
internal class EventsLogViewModel @Inject constructor(
private val eventRepository: EventRepository,
) : ViewModel() {

private val _scopes = MutableLiveData<List<TroubleshootingItemViewData>>(emptyList())
val scopes: LiveData<List<TroubleshootingItemViewData>>
get() = _scopes

private val _events = MutableLiveData<List<TroubleshootingItemViewData>>(emptyList())
val events: LiveData<List<TroubleshootingItemViewData>>
get() = _events

fun collectEventScopes() {
viewModelScope.launch {
eventRepository.getAllScopes()
.map { scope -> formatScopeViewData(scope) }
.ifEmpty { listOf(TroubleshootingItemViewData(title = "No event scopes found")) }
.let { _scopes.postValue(it) }
}
}

fun collectEvents(scopeId: String) {
viewModelScope.launch {
eventRepository.getEventsFromScope(scopeId)
.map { event -> formatEventViewData(event) }
.reversed()
.ifEmpty { listOf(TroubleshootingItemViewData(title = "No events found")) }
.let { _events.postValue(it) }
}
}

private fun formatScopeViewData(scope: EventScope): TroubleshootingItemViewData =
TroubleshootingItemViewData(
title = scope.id,
subtitle = formatTimestampSubtitle(scope.createdAt.ms, scope.endedAt?.ms),
body = """
Type: ${scope.type} | End cause: ${scope.payload.endCause}
${scope.payload.language} | ${scope.payload.sidVersion} | Lib: ${scope.payload.libSimprintsVersion}
Configuration ID: ${scope.payload.projectConfigurationId}
""".trimIndent(),
navigationId = scope.id,
)

private fun formatEventViewData(event: Event): TroubleshootingItemViewData =
TroubleshootingItemViewData(
title = event.type.name,
subtitle = formatTimestampSubtitle(
event.payload.createdAt.ms,
event.payload.endedAt?.ms
),
body = "ID: ${event.id}\n" + event.payload.toSafeString(),
)

private fun formatTimestampSubtitle(startMs: Long, endMs: Long? = null): String =
"Started: ${Date(startMs)}" + endMs?.let { "\nEnded: ${Date(it)}" }.orEmpty()

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/troubleshootingList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />

<ProgressBar
android:id="@+id/troubleshootingListProgress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">

<com.google.android.material.appbar.AppBarLayout
android:id="@+id/troubleshootingAppBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">

<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/troubleshootingToolbar"
android:layout_width="match_parent"
android:layout_height="?android:attr/actionBarSize"
app:navigationContentDescription="back"
app:navigationIcon="?android:attr/homeAsUpIndicator"
app:title="Troubleshooting" />
</com.google.android.material.appbar.AppBarLayout>

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/troubleshootingList"
android:layout_width="match_parent"
android:layout_height="0dp"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/troubleshootingAppBar" />

<ProgressBar
android:id="@+id/troubleshootingListProgress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/troubleshootingAppBar" />

</androidx.constraintlayout.widget.ConstraintLayout>
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
android:layout_marginVertical="4dp"
app:cardBackgroundColor="#fff"
app:cardCornerRadius="8dp"
app:cardElevation="2dp">

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingHorizontal="8dp"
android:paddingTop="8dp">

<TextView
android:id="@+id/troubleshootingItemTitle"
style="@style/Text.Body1.Bold"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:maxLines="2"
android:textIsSelectable="true"
app:layout_constraintEnd_toStartOf="@id/troubleshootingItemCopy"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Event scope id" />

<ImageView
android:id="@+id/troubleshootingItemCopy"
android:layout_width="36dp"
android:layout_height="36dp"
android:clickable="true"
android:focusable="true"
android:padding="4dp"
android:src="@drawable/ic_copy"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="@color/simprints_text_grey_light" />

<TextView
android:id="@+id/troubleshootingItemSubtitle"
style="@style/Text.Body2.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:maxLines="4"
android:textIsSelectable="true"
app:layout_constraintEnd_toStartOf="@id/troubleshootingItemCopy"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/troubleshootingItemTitle"
tools:text="Timestamps\nTimestamps" />

<TextView
android:id="@+id/troubleshootingItemBody"
style="@style/Text.Body1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:maxLines="8"
android:textIsSelectable="true"
app:layout_constraintBottom_toTopOf="@id/troubleshootingItemButton"
app:layout_constraintTop_toBottomOf="@id/troubleshootingItemSubtitle"
app:layout_goneMarginBottom="8dp"
tools:text="@tools:sample/lorem/random" />

<com.google.android.material.button.MaterialButton
android:id="@+id/troubleshootingItemButton"
style="@style/Widget.Simprints.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Details"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/troubleshootingItemBody"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</FrameLayout>
Loading