Skip to content

Commit

Permalink
232 - Implement error handling for search, added log debugging for se…
Browse files Browse the repository at this point in the history
…arch caches. (#54)

* Implement error handling for package search, better debugging for search caches.

Added new functionality to handle search errors during package search, allowing users to retry the search. Included visual error representation in the UI with a retry link. The search can now also be retried programmatically if it fails.

* Allow search error message to be collapsed.
  • Loading branch information
lamba92 authored Feb 2, 2024
1 parent 73f697e commit a312e73
Show file tree
Hide file tree
Showing 9 changed files with 188 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import com.jetbrains.packagesearch.plugin.core.data.PackageSearchDeclaredPackage
import com.jetbrains.packagesearch.plugin.core.services.PackageSearchProjectCachesService
import java.nio.file.Path
import kotlin.contracts.contract
import kotlin.coroutines.cancellation.CancellationException
import kotlinx.coroutines.channels.ProducerScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
Expand Down Expand Up @@ -308,4 +309,6 @@ class ProjectDataImportListenerAdapter(private val project: Project) : ProjectDa
override fun onFinalTasksFinished(projectPath: String?) {
project.service<State>().value = false
}
}
}

fun <T> Result<T>.suspendSafe() = onFailure { if (it is CancellationException) throw it }
Original file line number Diff line number Diff line change
Expand Up @@ -278,24 +278,60 @@ class PackageListBuilder(
additionalContent = search.buildVariantsText()
)

is Search.Results.Base -> addFromSearchQueryBase(
is Search.Response.Base.Success -> addFromSearchQueryBase(
headerId = headerId as PackageListItem.Header.Id.Remote.Base,
search = search,
module = modulesMap[headerId.moduleIdentity] as? PackageSearchModule.Base ?: return@forEach
)

is Search.Results.WithVariants -> addFromSearchQueryWithVariants(
is Search.Response.WithVariants.Success -> addFromSearchQueryWithVariants(
headerId = headerId as PackageListItem.Header.Id.Remote.WithVariant,
search = search,
module = modulesMap[headerId.moduleIdentity] as? PackageSearchModule.WithVariants ?: return@forEach
)

is Search.Response.Base.Error -> addSearchResultError(headerId = headerId)

is Search.Response.WithVariants.Error -> addSearchResultError(
headerId = headerId,
attributes = search.attributes,
additionalContent = search.buildVariantsText()
)
}
}
}

private fun addSearchResultError(
headerId: PackageListItem.Header.Id.Remote,
attributes: List<String> = emptyList(),
additionalContent: PackageListItem.Header.AdditionalContent.VariantsText? = null,
) {
val headerState = when (headerCollapsedStates[headerId]) {
TargetState.OPEN -> PackageListItem.Header.State.OPEN
else -> PackageListItem.Header.State.CLOSED
}
addHeader(
title = PackageSearchBundle.message("packagesearch.ui.toolwindow.tab.packages.searchResults"),
id = headerId,
state = headerState,
attributes = attributes,
additionalContent = additionalContent,
)
if (headerState == PackageListItem.Header.State.OPEN) {
items.add(
PackageListItem.SearchError(
id = PackageListItem.SearchError.Id(
headerId.moduleIdentity,
headerId
)
)
)
}
}

private fun addFromSearchQueryWithVariants(
headerId: PackageListItem.Header.Id.Remote.WithVariant,
search: Search.Results.WithVariants,
search: Search.Response.WithVariants.Success,
module: PackageSearchModule.WithVariants,
) {
val state = when (headerCollapsedStates[headerId]) {
Expand Down Expand Up @@ -347,7 +383,7 @@ class PackageListBuilder(

private fun addFromSearchQueryBase(
headerId: PackageListItem.Header.Id.Remote.Base,
search: Search.Results.Base,
search: Search.Response.Base.Success,
module: PackageSearchModule.Base,
) {
val state = when (headerCollapsedStates[headerId]) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import kotlinx.serialization.Serializable

sealed interface PackageListItem {

val title: String
val id: Id

@Serializable
Expand All @@ -15,7 +14,7 @@ sealed interface PackageListItem {
}

data class Header(
override val title: String,
val title: String,
override val id: Id,
val state: State,
val attributes: List<String> = emptyList(),
Expand All @@ -39,6 +38,7 @@ sealed interface PackageListItem {
sealed interface Declared : Id {
@Serializable
data class Base(override val moduleIdentity: PackageSearchModule.Identity) : Declared

@Serializable
data class WithVariant(
override val moduleIdentity: PackageSearchModule.Identity,
Expand All @@ -49,8 +49,10 @@ sealed interface PackageListItem {

@Serializable
sealed interface Remote : Id {

@Serializable
data class Base(override val moduleIdentity: PackageSearchModule.Identity) : Remote

@Serializable
data class WithVariant(
override val moduleIdentity: PackageSearchModule.Identity,
Expand All @@ -63,6 +65,8 @@ sealed interface PackageListItem {

sealed interface Package : PackageListItem {

val title: String

@Serializable
sealed interface Id : PackageListItem.Id {
val packageId: String
Expand Down Expand Up @@ -121,7 +125,7 @@ sealed interface PackageListItem {
override val moduleIdentity: PackageSearchModule.Identity,
override val packageId: String,
val headerId: Header.Id.Remote.Base,
) : Remote.Id
) : Remote.Id
}

data class WithVariant(
Expand All @@ -143,4 +147,14 @@ sealed interface PackageListItem {
}
}
}

data class SearchError(override val id: Id) : PackageListItem {

@Serializable
data class Id(
override val moduleIdentity: PackageSearchModule.Identity,
val parentHeaderId: Header.Id,
) : PackageListItem.Id
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,9 @@ sealed interface PackageListItemEvent {
data class GoToSource(override val eventId: PackageListItem.Package.Declared.Id) : OnPackageAction

}

@Serializable
data class OnRetryPackageSearch(
override val eventId: PackageListItem.SearchError.Id,
) : PackageListItemEvent
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import com.jetbrains.packagesearch.plugin.core.data.PackageSearchDependencyManag
import com.jetbrains.packagesearch.plugin.core.data.PackageSearchModule
import com.jetbrains.packagesearch.plugin.core.data.PackageSearchModuleEditor
import com.jetbrains.packagesearch.plugin.core.utils.IntelliJApplication
import com.jetbrains.packagesearch.plugin.core.utils.replayOn
import com.jetbrains.packagesearch.plugin.fus.logGoToSource
import com.jetbrains.packagesearch.plugin.fus.logHeaderAttributesClick
import com.jetbrains.packagesearch.plugin.fus.logHeaderVariantsClick
Expand All @@ -37,7 +38,6 @@ import com.jetbrains.packagesearch.plugin.utils.PackageSearchApplicationCachesSe
import com.jetbrains.packagesearch.plugin.utils.PackageSearchProjectService
import com.jetbrains.packagesearch.plugin.utils.logTODO
import com.jetbrains.packagesearch.plugin.utils.logWarn
import com.jetbrains.packagesearch.plugin.utils.searchPackages
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
Expand Down Expand Up @@ -138,6 +138,9 @@ class PackageListViewModel(private val project: Project) : Disposable {
val searchQuery: String,
)

private val restartSearchChannel = Channel<Unit>()
private val restartSearchFlow = restartSearchChannel.consumeAsFlow()

private val searchResultMapFlow: StateFlow<Map<PackageListItem.Header.Id.Remote, Search>> = combine(
selectedModulesFlow,
searchQueryStateFlow
Expand All @@ -148,8 +151,9 @@ class PackageListViewModel(private val project: Project) : Disposable {
else -> null
}
}
.replayOn(restartSearchFlow)
.mapLatest { data ->
when (data) {
val map: Map<PackageListItem.Header.Id.Remote, Search> = when (data) {
null -> emptyMap()
else -> {
isLoadingChannel.send(true)
Expand All @@ -160,17 +164,18 @@ class PackageListViewModel(private val project: Project) : Disposable {
}
}
}
map
}
.onEach { isLoadingChannel.send(false) }
.modifiedBy(headerCollapsedStatesFlow) { current: Map<PackageListItem.Header.Id.Remote, Search>, change ->
.modifiedBy(headerCollapsedStatesFlow) { current, change ->
current.mapValues { (id, value) ->
when {
change[id] == OPEN && value is Search.Query -> value.execute()
else -> value
}
}
}
.modifiedBy(selectedModulesFlow) { current: Map<PackageListItem.Header.Id.Remote, Search>, change ->
.modifiedBy(selectedModulesFlow) { current, change ->
val changeIdentities = change.map { it.identity }
if (current.keys.any { it.moduleIdentity !in changeIdentities }) {
emptyMap()
Expand Down Expand Up @@ -215,23 +220,22 @@ class PackageListViewModel(private val project: Project) : Disposable {

private suspend fun PackageSearchModule.Base.getSearchQuery(
searchQuery: String,
): Map<PackageListItem.Header.Id.Remote, Search.Results.Base> {
): Map<PackageListItem.Header.Id.Remote, Search.Response.Base> {
val headerId = PackageListItem.Header.Id.Remote.Base(identity)
val results = Search.Results.Base(
packages = IntelliJApplication.PackageSearchApplicationCachesService
.apiPackageCache
.searchPackages(buildSearchParameters {
this.searchQuery = searchQuery
packagesType = compatiblePackageTypes
}),
)
val response = Search.Query.Base(
query = buildSearchParameters {
this.searchQuery = searchQuery
packagesType = compatiblePackageTypes
},
apis = IntelliJApplication.PackageSearchApplicationCachesService.apiPackageCache,
).execute()
headerCollapsedStatesFlow.update { current ->
when (headerId) {
!in current -> current + (headerId to OPEN)
else -> current
}
}
return mapOf(headerId to results)
return mapOf(headerId to response)
}

private suspend fun PackageSearchModule.WithVariants.getSearchQueries(
Expand All @@ -251,27 +255,26 @@ class PackageListViewModel(private val project: Project) : Disposable {
val primaryVariantName = variants.first { it.isPrimary }.name
val attributes = variants.first().attributes.map { it.value }
val additionalVariants = variants.map { it.name } - primaryVariantName
val search: Search = when (mainVariantName) {
val search = when (mainVariantName) {
in variants.map { it.name } -> {
val results = Search.Results.WithVariants(
packages = IntelliJApplication.PackageSearchApplicationCachesService
.apiPackageCache
.searchPackages {
this.searchQuery = searchQuery
this.packagesType = packagesType
},
val response = Search.Query.WithVariants(
query = buildSearchParameters {
this.searchQuery = searchQuery
this.packagesType = packagesType
},
apis = IntelliJApplication.PackageSearchApplicationCachesService.apiPackageCache,
attributes = attributes,
primaryVariantName = primaryVariantName,
additionalVariants = additionalVariants,
)
).execute()
headerCollapsedStatesFlow.update { current ->
when (headerId) {
!in current -> current + (headerId to OPEN)
else -> current
}

}
results
response
}

else -> Search.Query.WithVariants(
Expand Down Expand Up @@ -327,10 +330,15 @@ class PackageListViewModel(private val project: Project) : Disposable {
is PackageListItemEvent.OnPackageAction.Update -> handle(event)
is PackageListItemEvent.SetHeaderState -> handle(event)
is PackageListItemEvent.UpdateAllPackages -> handle(event)
is PackageListItemEvent.OnRetryPackageSearch -> handle(event)
}
}
}

private suspend fun handle(event: PackageListItemEvent.OnRetryPackageSearch) {
restartSearchChannel.send(Unit)
}

private fun handle(event: PackageListItemEvent.InfoPanelEvent.OnHeaderVariantsClick) {
logTODO()
logHeaderVariantsClick()
Expand All @@ -353,7 +361,7 @@ class PackageListViewModel(private val project: Project) : Disposable {
when (event.eventId) {
is PackageListItem.Package.Remote.Base.Id -> {
val headerId = PackageListItem.Header.Id.Remote.Base(event.eventId.moduleIdentity)
val search = searchResultMapFlow.value[headerId] as? Search.Results.Base
val search = searchResultMapFlow.value[headerId] as? Search.Response.Base.Success
?: return
infoPanelViewModel.setPackage(
module = event.eventId.getModule() as? PackageSearchModule.Base ?: return,
Expand All @@ -367,8 +375,9 @@ class PackageListViewModel(private val project: Project) : Disposable {
event.eventId.moduleIdentity,
event.eventId.headerId.compatibleVariantNames
)
val search = searchResultMapFlow.value[headerId] as? Search.Results.WithVariants
?: return
val search =
searchResultMapFlow.value[headerId] as? Search.Response.WithVariants.Success
?: return
infoPanelViewModel.setPackage(
module = event.eventId.getModule() as? PackageSearchModule.WithVariants ?: return,
apiPackage = search.packages.firstOrNull { it.id == event.eventId.packageId } ?: return,
Expand Down Expand Up @@ -467,7 +476,7 @@ class PackageListViewModel(private val project: Project) : Disposable {
val variant = module.variants[actionType.selectedVariantName]
?: return
val search = searchResultMapFlow
.value[actionType.headerId] as? Search.Results.WithVariants
.value[actionType.headerId] as? Search.Response.WithVariants.Success
?: return
val apiPackage = search.packages
.firstOrNull { it.id == actionType.eventId.packageId }
Expand All @@ -486,7 +495,8 @@ class PackageListViewModel(private val project: Project) : Disposable {
val module = actionType.eventId
.getModule() as? PackageSearchModule.Base
?: return
val search = searchResultMapFlow.value[actionType.headerId] as? Search.Results ?: return
val search =
searchResultMapFlow.value[actionType.headerId] as? Search.Response.Base.Success ?: return
val apiPackage = search.packages
.firstOrNull { it.id == actionType.eventId.packageId }
?: return
Expand Down
Loading

0 comments on commit a312e73

Please sign in to comment.