Skip to content

Commit 8a8de69

Browse files
authored
Add widget for weekly downloads chart with downloads data (#8208)
1 parent 0494d15 commit 8a8de69

File tree

9 files changed

+607
-0
lines changed

9 files changed

+607
-0
lines changed

app/lib/frontend/handlers/package.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import 'package:_pub_shared/data/advisories_api.dart'
1010
import 'package:_pub_shared/utils/sdk_version_cache.dart';
1111
import 'package:meta/meta.dart';
1212
import 'package:neat_cache/neat_cache.dart';
13+
import 'package:pub_dev/service/download_counts/computations.dart';
1314
import 'package:pub_dev/service/security_advisories/backend.dart';
1415
import 'package:pub_dev/shared/versions.dart';
1516
import 'package:pub_dev/task/backend.dart';
@@ -453,6 +454,8 @@ Future<PackagePageData> loadPackagePageData(
453454
final scoreCardFuture = scoreCardBackend
454455
.getScoreCardData(packageName, versionName, package: package);
455456

457+
final weeklyDownloadCountsFuture = getWeeklyDownloads(package.name!);
458+
456459
await Future.wait([
457460
latestReleasesFuture,
458461
isLikedFuture,
@@ -461,6 +464,7 @@ Future<PackagePageData> loadPackagePageData(
461464
assetFuture,
462465
isAdminFuture,
463466
scoreCardFuture,
467+
weeklyDownloadCountsFuture,
464468
]);
465469

466470
final selectedVersion = await selectedVersionFuture;
@@ -484,6 +488,7 @@ Future<PackagePageData> loadPackagePageData(
484488
scoreCard: await scoreCardFuture,
485489
isAdmin: await isAdminFuture,
486490
isLiked: await isLikedFuture,
491+
weeklyDownloadCounts: await weeklyDownloadCountsFuture,
487492
);
488493
}
489494

app/lib/frontend/templates/views/pkg/info_box.dart

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import 'package:_pub_shared/format/encoding.dart';
56
import 'package:pana/pana.dart';
7+
import 'package:pub_dev/service/download_counts/download_counts.dart';
68
import 'package:pubspec_parse/pubspec_parse.dart' as pubspek;
79

810
import '../../../../package/models.dart';
@@ -69,6 +71,8 @@ d.Node packageInfoBoxNode({
6971
}
7072
return d.fragment([
7173
labeledScores,
74+
if (data.weeklyDownloadCounts != null)
75+
_downloadsChart(data.weeklyDownloadCounts!),
7276
if (thumbnailUrl != null)
7377
d.div(classes: [
7478
'detail-screenshot-thumbnail'
@@ -105,6 +109,23 @@ d.Node packageInfoBoxNode({
105109
]);
106110
}
107111

112+
d.Node _downloadsChart(WeeklyDownloadCounts wdc) {
113+
final container = d.div(
114+
classes: ['weekly-downloads-sparkline'],
115+
id: '-weekly-downloads-sparkline',
116+
attributes: {
117+
'data-widget': 'weekly-sparkline',
118+
'data-weekly-sparkline-points':
119+
_encodeForWeeklySparkline(wdc.weeklyDownloads, wdc.newestDate),
120+
});
121+
return container;
122+
}
123+
124+
String _encodeForWeeklySparkline(List<int> downloads, DateTime newestDate) {
125+
final date = newestDate.toUtc().millisecondsSinceEpoch ~/ 1000;
126+
return encodeIntsAsLittleEndianBase64String([date, ...downloads]);
127+
}
128+
108129
d.Node _publisher(String? publisherId) {
109130
return _block(
110131
'Publisher',

app/lib/package/models.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import 'package:clock/clock.dart';
1010
import 'package:json_annotation/json_annotation.dart';
1111
import 'package:pana/models.dart';
1212
import 'package:pub_dev/service/download_counts/backend.dart';
13+
import 'package:pub_dev/service/download_counts/download_counts.dart';
1314
import 'package:pub_dev/shared/markdown.dart';
1415
import 'package:pub_semver/pub_semver.dart';
1516

@@ -1132,6 +1133,7 @@ class PackagePageData {
11321133
final ScoreCardData scoreCard;
11331134
final bool isAdmin;
11341135
final bool isLiked;
1136+
final WeeklyDownloadCounts? weeklyDownloadCounts;
11351137
PackageView? _view;
11361138

11371139
PackagePageData({
@@ -1143,6 +1145,7 @@ class PackagePageData {
11431145
required this.scoreCard,
11441146
required this.isAdmin,
11451147
required this.isLiked,
1148+
required this.weeklyDownloadCounts,
11461149
}) : latestReleases = latestReleases ?? package.latestReleases;
11471150

11481151
bool get hasReadme => versionInfo.assets.contains(AssetKind.readme);

app/lib/service/download_counts/computations.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@ import 'dart:math';
66

77
import 'package:gcloud/storage.dart';
88
import 'package:pub_dev/service/download_counts/backend.dart';
9+
import 'package:pub_dev/service/download_counts/download_counts.dart';
910
import 'package:pub_dev/service/download_counts/models.dart';
1011
import 'package:pub_dev/shared/configuration.dart';
1112
import 'package:pub_dev/shared/storage.dart';
1213
import 'package:pub_dev/shared/utils.dart';
1314

15+
import '../../shared/redis_cache.dart' show cache;
16+
1417
Future<void> compute30DaysTotalTask() async {
1518
final allDownloadCounts = await downloadCountsBackend.listAllDownloadCounts();
1619
final totals = await compute30DayTotals(allDownloadCounts);
@@ -43,6 +46,17 @@ Future<void> upload30DaysTotal(Map<String, int> counts) async {
4346
jsonUtf8Encoder.convert(counts));
4447
}
4548

49+
Future<WeeklyDownloadCounts?> getWeeklyDownloads(String package) async {
50+
return (await cache.weeklyDownloadCounts(package).get(() async {
51+
final wdc = await computeWeeklyDownloads(package);
52+
if (wdc.newestDate == null) {
53+
return null;
54+
}
55+
return WeeklyDownloadCounts(
56+
weeklyDownloads: wdc.weeklyDownloads, newestDate: wdc.newestDate!);
57+
}));
58+
}
59+
4660
/// Computes `weeklyDownloads` starting from `newestDate` for [package].
4761
///
4862
/// Each number in `weeklyDownloads` is the total number of downloads for

app/lib/service/download_counts/download_counts.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,3 +215,18 @@ class CountData {
215215
_$CountDataFromJson(json);
216216
Map<String, dynamic> toJson() => _$CountDataToJson(this);
217217
}
218+
219+
@JsonSerializable(includeIfNull: false)
220+
class WeeklyDownloadCounts {
221+
final List<int> weeklyDownloads;
222+
final DateTime newestDate;
223+
224+
WeeklyDownloadCounts({
225+
required this.weeklyDownloads,
226+
required this.newestDate,
227+
});
228+
229+
factory WeeklyDownloadCounts.fromJson(Map<String, dynamic> json) =>
230+
_$WeeklyDownloadCountsFromJson(json);
231+
Map<String, dynamic> toJson() => _$WeeklyDownloadCountsToJson(this);
232+
}

app/lib/service/download_counts/download_counts.g.dart

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/lib/shared/redis_cache.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,16 @@ class CachePatterns {
247247
decode: (d) => CountData.fromJson(d as Map<String, dynamic>),
248248
))[package];
249249

250+
Entry<WeeklyDownloadCounts> weeklyDownloadCounts(String package) => _cache
251+
.withPrefix('weekly-download-counts/')
252+
.withTTL(Duration(hours: 6))
253+
.withCodec(utf8)
254+
.withCodec(json)
255+
.withCodec(wrapAsCodec(
256+
encode: (WeeklyDownloadCounts wdc) => wdc.toJson(),
257+
decode: (d) => WeeklyDownloadCounts.fromJson(d as Map<String, dynamic>),
258+
))[package];
259+
250260
Entry<List<LikeData>> userPackageLikes(String userId) => _cache
251261
.withPrefix('user-package-likes/')
252262
.withTTL(Duration(minutes: 60))

0 commit comments

Comments
 (0)