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
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ 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.feature.dashboard.settings.troubleshooting.workers.WorkerLogFragment
import com.simprints.infra.uibase.annotations.ExcludedFromGeneratedTestCoverageReports

@ExcludedFromGeneratedTestCoverageReports("UI code")
Expand All @@ -23,5 +24,6 @@ internal class TroubleshootingPagerAdapter(

Overview("Overview", { OverviewFragment() }),
Events("Event scopes", { EventScopeLogFragment() }),
Workers("Worker log", { WorkerLogFragment() }),
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.simprints.feature.dashboard.settings.troubleshooting.workers

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

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

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

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

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

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.work.WorkInfo
import androidx.work.WorkManager
import androidx.work.WorkQuery
import com.simprints.feature.dashboard.settings.troubleshooting.adapter.TroubleshootingItemViewData
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import java.util.Date
import javax.inject.Inject

@HiltViewModel
internal class WorkerLogViewModel @Inject constructor(
private val workManager: WorkManager,
) : ViewModel() {

private val _workers = MutableLiveData<List<TroubleshootingItemViewData>>(emptyList())
val workers: LiveData<List<TroubleshootingItemViewData>>
get() = _workers

fun collectWorkerData() {
viewModelScope.launch {
workManager.getWorkInfosFlow(WorkQuery.fromStates(WorkInfo.State.entries))
.collect { infos ->
infos.map { formatWorkInfo(it) }
.take(50)
.ifEmpty { listOf(TroubleshootingItemViewData("No data")) }
.let { _workers.postValue(it) }
}
}
}

private fun formatWorkInfo(info: WorkInfo) = TroubleshootingItemViewData(
// One of the work info tags is worker's full class name
title = info.tags
.find { it.startsWith("com.simprints") }
?.substringAfterLast(".")
?: info.id.toString(),
subtitle = info.state.toString(),
body = if (info.state == WorkInfo.State.ENQUEUED) {
"ID: ${info.id}\nNext run: ${Date(info.nextScheduleTimeMillis)}"
} else {
"ID: ${info.id}\nOutput: ${info.outputData}"
}


)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package com.simprints.feature.dashboard.settings.troubleshooting.workers

import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.work.WorkInfo
import androidx.work.WorkManager
import com.google.common.truth.Truth.assertThat
import com.jraska.livedata.test
import com.simprints.testtools.common.coroutines.TestCoroutineRule
import io.mockk.MockKAnnotations
import io.mockk.coEvery
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.mockk
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.util.UUID

class WorkerLogViewModelTest {

@get:Rule
val rule = InstantTaskExecutorRule()

@get:Rule
val testCoroutineRule = TestCoroutineRule()

@MockK
private lateinit var workManager: WorkManager

private lateinit var viewModel: WorkerLogViewModel

@Before
fun setUp() {
MockKAnnotations.init(this, relaxed = true)

viewModel = WorkerLogViewModel(
workManager = workManager,
)
}

@Test
fun `sets list of scopes on request`() = runTest {
coEvery { workManager.getWorkInfosFlow(any()) } returns flowOf(listOf(
mockk<WorkInfo>(relaxed = true) {
every { id } returns UUID.fromString("c92d4da1-dc9a-4e25-9fcd-a9aca78a4cf3")
every { tags } returns setOf("com.simprints.Worker")
every { state } returns WorkInfo.State.SUCCEEDED
}
))

val workers = viewModel.workers.test()
viewModel.collectWorkerData()

assertThat(workers.value()).isNotEmpty()
assertThat(workers.value().first().title).isEqualTo("Worker")
assertThat(workers.value().first().body).contains("Output")
}

@Test
fun `sets list of scopes placeholder if no scopes`() = runTest {
coEvery { workManager.getWorkInfosFlow(any()) } returns flowOf(emptyList())

val workers = viewModel.workers.test()
viewModel.collectWorkerData()

assertThat(workers.value()).isNotEmpty()
}

@Test
fun `sets id to UUID if no tag`() = runTest {
coEvery { workManager.getWorkInfosFlow(any()) } returns flowOf(listOf(
mockk<WorkInfo>(relaxed = true) {
every { id } returns UUID.fromString("c92d4da1-dc9a-4e25-9fcd-a9aca78a4cf3")
every { tags } returns setOf("Worker")
}
))

val workers = viewModel.workers.test()
viewModel.collectWorkerData()

assertThat(workers.value()).isNotEmpty()
assertThat(workers.value().first().title).isEqualTo("c92d4da1-dc9a-4e25-9fcd-a9aca78a4cf3")
}

@Test
fun `sets takes next scheduled time if worked is enqueued`() = runTest {
coEvery { workManager.getWorkInfosFlow(any()) } returns flowOf(listOf(
mockk<WorkInfo>(relaxed = true) {
every { state } returns WorkInfo.State.ENQUEUED
}
))

val workers = viewModel.workers.test()
viewModel.collectWorkerData()

assertThat(workers.value()).isNotEmpty()
assertThat(workers.value().first().body).contains("Next run:")
}
}