From ac223d376e880ea1d947a541a342ec473c973cfc Mon Sep 17 00:00:00 2001 From: Fabrizio Scarponi <36624359+fscarponi@users.noreply.github.com> Date: Fri, 10 May 2024 11:25:40 +0200 Subject: [PATCH] Add 'No Packages Found' view to Package List UI (#159) (#163) * Add 'No Packages Found' view to Package List UI (#159) This commit introduces a new view in the package search results for the case when no packages are found. The update includes a 'NoPackagesFoundItem' Composable function, and respective user interface changes. The 'NoPackagesFound' class added to the 'PackageListItem' differentiates between errors and no results in package searches. (cherry picked from commit f8e8135ecf06c5b3ff722a41ef4f9b5bc0fe112a) * Remove unused imports in ProjectServiceTest Cleaned up PackageSearchProjectServiceTest file by removing unnecessary imports. This helps enhance code readability and maintainability by removing clutter from the source file. --------- Co-authored-by: Lamberto Basti --- .../model/packageslist/PackageListBuilder.kt | 154 +++++++++++------- .../ui/model/packageslist/PackageListItem.kt | 9 + .../packages/PackageSearchPackageList.kt | 39 ++++- .../messages/packageSearchBundle.properties | 1 + .../PackageSearchProjectServiceTest.kt | 3 - 5 files changed, 142 insertions(+), 64 deletions(-) diff --git a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/packageslist/PackageListBuilder.kt b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/packageslist/PackageListBuilder.kt index 62abd8f7..84370ba8 100644 --- a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/packageslist/PackageListBuilder.kt +++ b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/packageslist/PackageListBuilder.kt @@ -93,19 +93,19 @@ class PackageListBuilder( dependenciesToShow .filter { it.matchesSearchQuery() } .forEach { dependency -> - addDeclaredPackage( - title = dependency.displayName, - subtitle = dependency.coordinates, - id = PackageListItem.Package.Declared.Id.Base(base.identity, dependency.id), - icon = dependency.icon, - latestVersion = dependency.getLatestVersion(onlyStable)?.versionName, - selectedScope = dependency.declaredScope, - availableScopes = base.availableScopes, - declaredVersion = dependency.declaredVersion?.versionName, - availableVersions = dependency.getAvailableVersionStrings(), - allowMissingScope = !base.dependencyMustHaveAScope, - ) - } + addDeclaredPackage( + title = dependency.displayName, + subtitle = dependency.coordinates, + id = PackageListItem.Package.Declared.Id.Base(base.identity, dependency.id), + icon = dependency.icon, + latestVersion = dependency.getLatestVersion(onlyStable)?.versionName, + selectedScope = dependency.declaredScope, + availableScopes = base.availableScopes, + declaredVersion = dependency.declaredVersion?.versionName, + availableVersions = dependency.getAvailableVersionStrings(), + allowMissingScope = !base.dependencyMustHaveAScope, + ) + } } } @@ -193,23 +193,23 @@ class PackageListBuilder( variant.declaredDependencies .filter { it.matchesSearchQuery() } .forEach { dependency -> - addDeclaredPackage( - title = dependency.displayName, - subtitle = dependency.coordinates, - id = PackageListItem.Package.Declared.Id.WithVariant( - moduleIdentity = module.identity, - packageId = dependency.id, - variantName = variant.name, - ), - icon = dependency.icon, - latestVersion = dependency.getLatestVersion(onlyStable)?.versionName, - selectedScope = dependency.declaredScope, - availableScopes = variant.availableScopes, - declaredVersion = dependency.declaredVersion?.versionName, - availableVersions = dependency.getAvailableVersionStrings(), - allowMissingScope = !module.dependencyMustHaveAScope, - ) - } + addDeclaredPackage( + title = dependency.displayName, + subtitle = dependency.coordinates, + id = PackageListItem.Package.Declared.Id.WithVariant( + moduleIdentity = module.identity, + packageId = dependency.id, + variantName = variant.name, + ), + icon = dependency.icon, + latestVersion = dependency.getLatestVersion(onlyStable)?.versionName, + selectedScope = dependency.declaredScope, + availableScopes = variant.availableScopes, + declaredVersion = dependency.declaredVersion?.versionName, + availableVersions = dependency.getAvailableVersionStrings(), + allowMissingScope = !module.dependencyMustHaveAScope, + ) + } } } } @@ -235,23 +235,23 @@ class PackageListBuilder( dependenciesToShow .filter { it.second.matchesSearchQuery() } .forEach { (variant, dependency) -> - addDeclaredPackage( - title = dependency.displayName, - subtitle = variant.name, - id = PackageListItem.Package.Declared.Id.WithVariant( - module.identity, - dependency.id, - variant.name, - ), - icon = dependency.icon, - latestVersion = dependency.getLatestVersion(onlyStable)?.versionName, - selectedScope = dependency.declaredScope, - availableScopes = variant.availableScopes, - declaredVersion = dependency.declaredVersion?.versionName, - availableVersions = dependency.getAvailableVersionStrings(), - allowMissingScope = !module.dependencyMustHaveAScope, - ) - } + addDeclaredPackage( + title = dependency.displayName, + subtitle = variant.name, + id = PackageListItem.Package.Declared.Id.WithVariant( + module.identity, + dependency.id, + variant.name, + ), + icon = dependency.icon, + latestVersion = dependency.getLatestVersion(onlyStable)?.versionName, + selectedScope = dependency.declaredScope, + availableScopes = variant.availableScopes, + declaredVersion = dependency.declaredVersion?.versionName, + availableVersions = dependency.getAvailableVersionStrings(), + allowMissingScope = !module.dependencyMustHaveAScope, + ) + } } } @@ -278,17 +278,31 @@ class PackageListBuilder( additionalContent = search.buildVariantsText() ) - 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.Response.Base.Success -> when { + search.packages.isNotEmpty() -> addFromSearchQueryBase( + headerId = headerId as PackageListItem.Header.Id.Remote.Base, + search = search, + module = modulesMap[headerId.moduleIdentity] as? PackageSearchModule.Base ?: return@forEach + ) + + else -> addSearchResultNoPackages(headerId = headerId) + } + + is Search.Response.WithVariants.Success -> when { + search.packages.isNotEmpty() -> addFromSearchQueryWithVariants( + headerId = headerId as PackageListItem.Header.Id.Remote.WithVariant, + search = search, + module = modulesMap[headerId.moduleIdentity] as? PackageSearchModule.WithVariants + ?: return@forEach + ) + + else -> addSearchResultNoPackages( + headerId = headerId, + additionalContent = search.buildVariantsText(), + attributes = search.attributes + ) + } - 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) @@ -301,6 +315,32 @@ class PackageListBuilder( } } + private fun addSearchResultNoPackages( + headerId: PackageListItem.Header.Id.Remote, + attributes: List = 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, + ) + items.add( + PackageListItem.NoPackagesFound( + id = PackageListItem.NoPackagesFound.Id( + moduleIdentity = headerId.moduleIdentity, + parentHeaderId = headerId + ) + ) + ) + } + private fun addSearchResultError( headerId: PackageListItem.Header.Id.Remote, attributes: List = emptyList(), diff --git a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/packageslist/PackageListItem.kt b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/packageslist/PackageListItem.kt index 6314206a..9a9418a7 100644 --- a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/packageslist/PackageListItem.kt +++ b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/packageslist/PackageListItem.kt @@ -157,4 +157,13 @@ sealed interface PackageListItem { ) : PackageListItem.Id } + data class NoPackagesFound(override val id: Id) : PackageListItem { + + @Serializable + data class Id( + override val moduleIdentity: PackageSearchModule.Identity, + val parentHeaderId: Header.Id, + ) : PackageListItem.Id + } + } \ No newline at end of file diff --git a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/panels/packages/PackageSearchPackageList.kt b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/panels/packages/PackageSearchPackageList.kt index 88b08314..eca531fd 100644 --- a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/panels/packages/PackageSearchPackageList.kt +++ b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/panels/packages/PackageSearchPackageList.kt @@ -46,6 +46,21 @@ import com.jetbrains.packagesearch.plugin.ui.model.packageslist.PackageListItemE import com.jetbrains.packagesearch.plugin.ui.model.packageslist.PackageListItemEvent.OnPackageAction.Install import com.jetbrains.packagesearch.plugin.ui.model.packageslist.PackageListItemEvent.OnPackageAction.Remove import com.jetbrains.packagesearch.plugin.ui.panels.packages.items.PackageListHeader +import java.net.Socket +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.consumeAsFlow +import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.launch +import kotlinx.coroutines.yield +import kotlinx.serialization.decodeFromByteArray +import kotlinx.serialization.encodeToByteArray +import kotlinx.serialization.protobuf.ProtoBuf import org.jetbrains.jewel.foundation.lazy.SelectableLazyColumn import org.jetbrains.jewel.foundation.lazy.SelectableLazyItemScope import org.jetbrains.jewel.foundation.lazy.SelectableLazyListState @@ -110,6 +125,10 @@ fun PackageSearchPackageList( onLinkClick = { onPackageEvent(PackageListItemEvent.OnRetryPackageSearch(item.id)) } ) } + + is PackageListItem.NoPackagesFound -> item(key = item.id, contentType = item.contentType()) { + NoPackagesFoundItem() + } } } } @@ -127,6 +146,17 @@ fun SearchErrorItem(onLinkClick: () -> Unit) { } } +@Composable +fun NoPackagesFoundItem() { + Column( + Modifier.fillMaxSize().padding(20.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text(message("packagesearch.ui.toolwindow.tab.packages.searchResults.noPackages")) + } +} + @Composable internal fun SelectableLazyItemScope.PackageListItem( modifier: Modifier = Modifier, @@ -158,7 +188,7 @@ internal fun SelectableLazyItemScope.PackageListItem( onDoubleClick = { onPackageListItemEvent(OnPackageDoubleClick(content.id)) }, onClick = { //this event should be handled when you click on a selected package to refresh side panel content - if (isSelected ) onPackageListItemEvent( + if (isSelected) onPackageListItemEvent( PackageListItemEvent.InfoPanelEvent.OnSelectedPackageClick(content.id) ) } @@ -344,7 +374,7 @@ internal fun RemotePackageWithVariantsActionPopup( } if (!isInstalledInPrimaryVariant) { passiveItem { - Divider(Orientation.Horizontal,modifier = Modifier.padding(vertical = 4.dp)) + Divider(Orientation.Horizontal, modifier = Modifier.padding(vertical = 4.dp)) } selectableItem( selected = false, @@ -356,7 +386,7 @@ internal fun RemotePackageWithVariantsActionPopup( if (additionalVariants.isNotEmpty()) { passiveItem { - Divider(Orientation.Horizontal,modifier = Modifier.padding(vertical = 4.dp)) + Divider(Orientation.Horizontal, modifier = Modifier.padding(vertical = 4.dp)) } additionalVariants.forEach { selectableItem( @@ -400,7 +430,7 @@ internal fun DeclaredPackageActionPopup( } } passiveItem { - Divider(Orientation.Horizontal,modifier = Modifier.padding(vertical = 4.dp)) + Divider(Orientation.Horizontal, modifier = Modifier.padding(vertical = 4.dp)) } selectableItem( selected = false, @@ -424,6 +454,7 @@ private fun PackageListItem.contentType() = when (this) { is PackageListItem.Package.Declared -> "declared.package" is PackageListItem.Package.Remote -> "remote.package" is PackageListItem.SearchError -> "search.error" + is PackageListItem.NoPackagesFound -> "no.packages.found" } @Composable diff --git a/plugin/src/main/resources/messages/packageSearchBundle.properties b/plugin/src/main/resources/messages/packageSearchBundle.properties index 02c72c6c..dc8576d5 100644 --- a/plugin/src/main/resources/messages/packageSearchBundle.properties +++ b/plugin/src/main/resources/messages/packageSearchBundle.properties @@ -196,6 +196,7 @@ packagesearch.ui.toolwindow.packages.sort.by=Sort by: packagesearch.ui.toolwindow.tab.packages.installedPackages.addedIn=Added in {0} packagesearch.ui.toolwindow.tab.packages.searchResults=Search Results packagesearch.ui.toolwindow.tab.packages.searchResults.error=An error occurred while searching for dependencies. +packagesearch.ui.toolwindow.tab.packages.searchResults.noPackages=No packages found. packagesearch.ui.toolwindow.tab.packages.searchResults.error.retry=Try again packagesearch.ui.toolwindow.tab.packages.selectedPackage=Selected dependency packagesearch.ui.toolwindow.tab.packages.title=Manage diff --git a/plugin/src/test/kotlin/com/jetbrains/packagesearch/plugin/tests/projectservice/PackageSearchProjectServiceTest.kt b/plugin/src/test/kotlin/com/jetbrains/packagesearch/plugin/tests/projectservice/PackageSearchProjectServiceTest.kt index 2c739950..af9e0f81 100644 --- a/plugin/src/test/kotlin/com/jetbrains/packagesearch/plugin/tests/projectservice/PackageSearchProjectServiceTest.kt +++ b/plugin/src/test/kotlin/com/jetbrains/packagesearch/plugin/tests/projectservice/PackageSearchProjectServiceTest.kt @@ -2,7 +2,6 @@ package com.jetbrains.packagesearch.plugin.tests.projectservice import com.intellij.ide.starter.junit5.JUnit5StarterAssistant import com.intellij.ide.starter.runner.TestContainerImpl -import com.intellij.testIntegration.TestFailedLineManager import com.intellij.tools.ide.performanceTesting.commands.CommandChain import com.intellij.tools.ide.performanceTesting.commands.exitApp import com.intellij.tools.ide.performanceTesting.commands.waitForSmartMode @@ -31,8 +30,6 @@ import org.junit.jupiter.params.provider.MethodSource @ExtendWith(JUnit5StarterAssistant::class) abstract class PackageSearchProjectServiceTest { - - abstract fun getContext(): TestContainerImpl abstract val resourcePath: String