From bd6221141ff75f4f5804b7fd54667c531232fec4 Mon Sep 17 00:00:00 2001 From: Sarah Zakarias Date: Mon, 12 May 2025 10:58:53 +0000 Subject: [PATCH 1/2] Experimental: use trend scores in search and sorting --- app/lib/frontend/handlers/experimental.dart | 6 ++++++ app/lib/frontend/templates/_consts.dart | 6 ++++++ app/lib/search/backend.dart | 1 + app/lib/search/mem_index.dart | 12 ++++++++++++ app/lib/search/search_service.dart | 4 ++++ app/lib/search/search_service.g.dart | 2 ++ pkg/_pub_shared/lib/search/search_form.dart | 3 +++ 7 files changed, 34 insertions(+) diff --git a/app/lib/frontend/handlers/experimental.dart b/app/lib/frontend/handlers/experimental.dart index 249794622e..dce0493608 100644 --- a/app/lib/frontend/handlers/experimental.dart +++ b/app/lib/frontend/handlers/experimental.dart @@ -10,6 +10,10 @@ typedef PublicFlag = ({String name, String description}); const _publicFlags = { (name: 'example', description: 'Short description'), + ( + name: 'trending-search', + description: 'Show trending packages and search by trending scores' + ), }; final _allFlags = { @@ -88,6 +92,8 @@ class ExperimentalFlags { bool get isDarkModeDefault => isEnabled('dark-as-default'); + bool get showTrending => isEnabled('trending-search'); + String encodedAsCookie() => _enabled.join(':'); @override diff --git a/app/lib/frontend/templates/_consts.dart b/app/lib/frontend/templates/_consts.dart index 6c07eb49f2..c74da2fc64 100644 --- a/app/lib/frontend/templates/_consts.dart +++ b/app/lib/frontend/templates/_consts.dart @@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import 'package:_pub_shared/search/tags.dart'; +import 'package:pub_dev/frontend/request_context.dart'; import '../dom/dom.dart' as d; @@ -124,5 +125,10 @@ List getSortDicts(bool isSearch) { final removeId = isSearch ? 'listing_relevance' : 'search_relevance'; return [ ..._sortDicts.where((d) => d.id != removeId), + if (requestContext.experimentalFlags.showTrending) + SortDict( + id: 'trending', + label: 'trendin', + tooltip: 'Packages are sorted by trending.'), ]; } diff --git a/app/lib/search/backend.dart b/app/lib/search/backend.dart index b9fc34a7e3..b7828989b4 100644 --- a/app/lib/search/backend.dart +++ b/app/lib/search/backend.dart @@ -372,6 +372,7 @@ class SearchBackend { updated: p.lastVersionPublished!, readme: compactReadme(readmeAsset?.textContent), downloadCount: downloadCountsBackend.lookup30DaysTotalCounts(pv.package), + trendScore: downloadCountsBackend.lookupTrendScore(pv.package), likeCount: p.likes, grantedPoints: scoreCard.grantedPubPoints, maxPoints: scoreCard.maxPubPoints, diff --git a/app/lib/search/mem_index.dart b/app/lib/search/mem_index.dart index fce0fc6c70..c604bcde9d 100644 --- a/app/lib/search/mem_index.dart +++ b/app/lib/search/mem_index.dart @@ -43,6 +43,7 @@ class InMemoryPackageIndex { late final List _downloadsOrderedHits; late final List _likesOrderedHits; late final List _pointsOrderedHits; + late final List _trendingOrderedHits; // Contains all of the topics the index had seen so far. // TODO: consider moving this into a separate index @@ -121,6 +122,8 @@ class InMemoryPackageIndex { score: (doc) => doc.likeCount.toDouble()); _pointsOrderedHits = _rankWithComparator(_comparePoints, score: (doc) => doc.grantedPoints.toDouble()); + _trendingOrderedHits = _rankWithComparator(_compareTrending, + score: (doc) => doc.trendScore.toDouble()); } IndexInfo indexInfo() { @@ -289,6 +292,9 @@ class InMemoryPackageIndex { case SearchOrder.points: indexedHits = _pointsOrderedHits.whereInScores(packageScores); break; + case SearchOrder.trending: + indexedHits = _trendingOrderedHits.whereInScores(packageScores); + break; } // bound by offset and limit (or randomize items) @@ -532,6 +538,12 @@ class InMemoryPackageIndex { if (x != 0) return x; return _compareUpdated(a, b); } + + int _compareTrending(PackageDocument a, PackageDocument b) { + final x = -a.trendScore.compareTo(b.trendScore); + if (x != 0) return x; + return _compareUpdated(a, b); + } } class _TextResults { diff --git a/app/lib/search/search_service.dart b/app/lib/search/search_service.dart index 1bfff0ea9e..3ca8ddf9b9 100644 --- a/app/lib/search/search_service.dart +++ b/app/lib/search/search_service.dart @@ -79,6 +79,8 @@ class PackageDocument { /// The normalized score between [0.0-1.0] (1.0 being the most downloaded package). double? downloadScore; + final int trendScore; + final int likeCount; /// The normalized score between [0.0-1.0] (1.0 being the most liked package). @@ -110,6 +112,7 @@ class PackageDocument { List? tags, int? downloadCount, this.downloadScore, + int? trendScore, int? likeCount, this.likeScore, int? grantedPoints, @@ -121,6 +124,7 @@ class PackageDocument { }) : created = created ?? clock.now(), updated = updated ?? clock.now(), downloadCount = downloadCount ?? 0, + trendScore = trendScore ?? 0, likeCount = likeCount ?? 0, grantedPoints = grantedPoints ?? 0, maxPoints = maxPoints ?? 0, diff --git a/app/lib/search/search_service.g.dart b/app/lib/search/search_service.g.dart index b13b4d0f6a..05f1e994f7 100644 --- a/app/lib/search/search_service.g.dart +++ b/app/lib/search/search_service.g.dart @@ -21,6 +21,7 @@ PackageDocument _$PackageDocumentFromJson(Map json) => tags: (json['tags'] as List?)?.map((e) => e as String).toList(), downloadCount: (json['downloadCount'] as num?)?.toInt(), downloadScore: (json['downloadScore'] as num?)?.toDouble(), + trendScore: (json['trendScore'] as num?)?.toInt(), likeCount: (json['likeCount'] as num?)?.toInt(), likeScore: (json['likeScore'] as num?)?.toDouble(), grantedPoints: (json['grantedPoints'] as num?)?.toInt(), @@ -52,6 +53,7 @@ Map _$PackageDocumentToJson(PackageDocument instance) => 'tags': instance.tags, 'downloadCount': instance.downloadCount, 'downloadScore': instance.downloadScore, + 'trendScore': instance.trendScore, 'likeCount': instance.likeCount, 'likeScore': instance.likeScore, 'grantedPoints': instance.grantedPoints, diff --git a/pkg/_pub_shared/lib/search/search_form.dart b/pkg/_pub_shared/lib/search/search_form.dart index 2b11d232a7..da672d68ea 100644 --- a/pkg/_pub_shared/lib/search/search_form.dart +++ b/pkg/_pub_shared/lib/search/search_form.dart @@ -64,6 +64,9 @@ enum SearchOrder { /// Search order should be in decreasing pub points. points, + + /// Search order should be in decreasing trend score. + trending, } /// Returns null if [value] is not a recognized search order. From b0ea61528b73c16eeac1f71ac196852bc8c4dadd Mon Sep 17 00:00:00 2001 From: Sarah Zakarias Date: Tue, 13 May 2025 06:59:31 +0000 Subject: [PATCH 2/2] typo --- app/lib/frontend/templates/_consts.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/frontend/templates/_consts.dart b/app/lib/frontend/templates/_consts.dart index c74da2fc64..a763970b53 100644 --- a/app/lib/frontend/templates/_consts.dart +++ b/app/lib/frontend/templates/_consts.dart @@ -128,7 +128,7 @@ List getSortDicts(bool isSearch) { if (requestContext.experimentalFlags.showTrending) SortDict( id: 'trending', - label: 'trendin', + label: 'trending', tooltip: 'Packages are sorted by trending.'), ]; }