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
4 changes: 2 additions & 2 deletions app/lib/fake/backend/fake_download_counts.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ Future<void> generateFake30DaysTotals(Map<String, int> totals) async {
downloadCounts30DaysTotalsFileName, jsonUtf8Encoder.convert(totals));
}

Future<void> generateFakeTrendScores(Map<String, int> totals) async {
Future<void> generateFakeTrendScores(Map<String, double> trends) async {
await storageService
.bucket(activeConfiguration.reportsBucketName!)
.writeBytesWithRetry(trendScoreFileName, jsonUtf8Encoder.convert(totals));
.writeBytesWithRetry(trendScoreFileName, jsonUtf8Encoder.convert(trends));
}
4 changes: 2 additions & 2 deletions app/lib/search/search_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ class PackageDocument {
/// The normalized score between [0.0-1.0] (1.0 being the most downloaded package).
double? downloadScore;

final int trendScore;
final double trendScore;

final int likeCount;

Expand Down Expand Up @@ -112,7 +112,7 @@ class PackageDocument {
List<String>? tags,
int? downloadCount,
this.downloadScore,
int? trendScore,
double? trendScore,
int? likeCount,
this.likeScore,
int? grantedPoints,
Expand Down
2 changes: 1 addition & 1 deletion app/lib/search/search_service.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

60 changes: 32 additions & 28 deletions app/lib/service/download_counts/backend.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,42 +31,44 @@ class DownloadCountsBackend {
late CachedValue<Map<String, int>> _thirtyDaysTotals;
var _lastDownloadsData = (data: <String, int>{}, etag: '');

late CachedValue<Map<String, int>> _trendScores;
var _lastTrendData = (data: <String, int>{}, etag: '');
late CachedValue<Map<String, double>> _trendScores;
var _lastTrendData = (data: <String, double>{}, etag: '');

DownloadCountsBackend(this._db) {
_thirtyDaysTotals = CachedValue(
_thirtyDaysTotals = CachedValue<Map<String, int>>(
name: 'thirtyDaysTotalDownloadCounts',
maxAge: Duration(days: 14),
interval: Duration(minutes: 30),
updateFn: _updateThirtyDaysTotals);
_trendScores = CachedValue(
_trendScores = CachedValue<Map<String, double>>(
name: 'trendScores',
maxAge: Duration(days: 14),
interval: Duration(minutes: 30),
updateFn: _updateTrendScores);
}

Future<Map<String, int>> _updateThirtyDaysTotals() async {
return _fetchAndUpdateCachedData(
fileName: downloadCounts30DaysTotalsFileName,
currentCachedData: _lastDownloadsData,
updateCache: (data) => _lastDownloadsData = data,
errorContext: '30-days total download counts');
return _fetchAndUpdateCachedData<int>(
fileName: downloadCounts30DaysTotalsFileName,
currentCachedData: _lastDownloadsData,
updateCache: (data) => _lastDownloadsData = data,
errorContext: '30-days total download counts',
);
}

Future<Map<String, int>> _updateTrendScores() async {
return _fetchAndUpdateCachedData(
fileName: trendScoreFileName,
currentCachedData: _lastTrendData,
updateCache: (data) => _lastTrendData = data,
errorContext: 'trend scores');
Future<Map<String, double>> _updateTrendScores() async {
return _fetchAndUpdateCachedData<double>(
fileName: trendScoreFileName,
currentCachedData: _lastTrendData,
updateCache: (data) => _lastTrendData = data,
errorContext: 'trend scores',
);
}

Future<Map<String, int>> _fetchAndUpdateCachedData({
Future<Map<String, V>> _fetchAndUpdateCachedData<V>({
required String fileName,
required ({Map<String, int> data, String etag}) currentCachedData,
required void Function(({Map<String, int> data, String etag}) newData)
required ({Map<String, V> data, String etag}) currentCachedData,
required void Function(({Map<String, V> data, String etag}) newData)
updateCache,
required String errorContext,
}) async {
Expand All @@ -89,7 +91,7 @@ class DownloadCountsBackend {
.single,
);

final data = _parseJsonToMapStringInt(rawData, fileName);
final data = _parseJsonToMapStringV<V>(rawData, fileName);

final newData = (data: data, etag: info.etag);
updateCache(newData);
Expand All @@ -109,23 +111,25 @@ class DownloadCountsBackend {
}
}

Map<String, int> _parseJsonToMapStringInt(dynamic rawJson, String fileName) {
Map<String, V> _parseJsonToMapStringV<V>(dynamic rawJson, String fileName) {
if (rawJson is! Map) {
throw FormatException(
'Expected JSON for $fileName to be a Map, but got ${rawJson.runtimeType}');
throw FormatException('Expected JSON for $fileName to be a Map, but got'
' ${rawJson.runtimeType}');
}

final Map<String, int> result = {};
final Map<String, V> result = {};
for (final entry in rawJson.entries) {
if (entry.key is! String) {
throw FormatException(
'Expected map keys for $fileName to be String, but found ${entry.key.runtimeType}');
'Expected map keys for $fileName to be String, but found'
' ${entry.key.runtimeType}');
}
if (entry.value is! int) {
if (entry.value is! V) {
throw FormatException(
'Expected map value for key "${entry.key}" in $fileName to be int, but got ${entry.value.runtimeType}');
'Expected map value for key "${entry.key}" in $fileName to be'
' ${V.runtimeType}, but got ${entry.value.runtimeType}');
}
result[entry.key as String] = entry.value as int;
result[entry.key as String] = entry.value as V;
}
return result;
}
Expand All @@ -143,7 +147,7 @@ class DownloadCountsBackend {
int? lookup30DaysTotalCounts(String package) =>
_thirtyDaysTotals.isAvailable ? _thirtyDaysTotals.value![package] : null;

int? lookupTrendScore(String package) =>
double? lookupTrendScore(String package) =>
_trendScores.isAvailable ? _trendScores.value![package] : null;

Future<CountData?> lookupDownloadCountData(String pkg) async {
Expand Down
16 changes: 8 additions & 8 deletions app/test/service/download_counts/computations_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -339,22 +339,22 @@ void main() {
});

testWithProfile('cache package trend scores', fn: () async {
await generateFakeTrendScores({'foo': 3, 'bar': 1, 'baz': 2});
await generateFakeTrendScores({'foo': 3.0, 'bar': 1.0, 'baz': 2.0});
expect(downloadCountsBackend.lookupTrendScore('foo'), isNull);
expect(downloadCountsBackend.lookupTrendScore('bar'), isNull);
expect(downloadCountsBackend.lookupTrendScore('baz'), isNull);

await downloadCountsBackend.start();
expect(downloadCountsBackend.lookupTrendScore('foo'), 3);
expect(downloadCountsBackend.lookupTrendScore('bar'), 1);
expect(downloadCountsBackend.lookupTrendScore('baz'), 2);
expect(downloadCountsBackend.lookupTrendScore('foo'), 3.0);
expect(downloadCountsBackend.lookupTrendScore('bar'), 1.0);
expect(downloadCountsBackend.lookupTrendScore('baz'), 2.0);
expect(downloadCountsBackend.lookupTrendScore('bax'), isNull);

await generateFakeTrendScores({'foo': 9, 'bar': 2, 'baz': 5});
await generateFakeTrendScores({'foo': 9.0, 'bar': 2.0, 'baz': 5.0});
await downloadCountsBackend.start();
expect(downloadCountsBackend.lookupTrendScore('foo'), 9);
expect(downloadCountsBackend.lookupTrendScore('bar'), 2);
expect(downloadCountsBackend.lookupTrendScore('baz'), 5);
expect(downloadCountsBackend.lookupTrendScore('foo'), 9.0);
expect(downloadCountsBackend.lookupTrendScore('bar'), 2.0);
expect(downloadCountsBackend.lookupTrendScore('baz'), 5.0);
expect(downloadCountsBackend.lookupTrendScore('bax'), isNull);
});
}