Skip to content

Commit d63cca6

Browse files
committed
Prepare widget with data for versions downloads chart
1 parent a1a879c commit d63cca6

File tree

10 files changed

+376
-29
lines changed

10 files changed

+376
-29
lines changed

app/lib/frontend/handlers/package.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -454,7 +454,7 @@ Future<PackagePageData> loadPackagePageData(
454454
final scoreCardFuture = scoreCardBackend
455455
.getScoreCardData(packageName, versionName, package: package);
456456

457-
final weeklyDownloadCountsFuture = getWeeklyDownloads(package.name!);
457+
final weeklyDownloadCountsFuture = getWeeklyTotalDownloads(package.name!);
458458

459459
await Future.wait([
460460
latestReleasesFuture,

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
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:_pub_shared/format/number_format.dart';
67
import 'package:pana/models.dart';
8+
import 'package:pub_dev/service/download_counts/download_counts.dart';
79
import 'package:pub_dev/shared/popularity_storage.dart';
810

911
import '../../../../scorecard/models.dart' hide ReportStatus;
@@ -77,6 +79,8 @@ d.Node scoreTabNode({
7779
],
7880
),
7981
_reportNode(report),
82+
if (card.weeklyVersionsDownloads != null)
83+
_versionDownloadsChart(card.weeklyVersionsDownloads!),
8084
if (toolEnvInfo != null) toolEnvInfo,
8185
]),
8286
if (!showPending)
@@ -173,6 +177,39 @@ d.Node _section(ReportSection section) {
173177
]);
174178
}
175179

180+
d.Node _versionDownloadsChart(
181+
WeeklyVersionsDownloadCounts weeklyVersionsDownloads) {
182+
return d.div(
183+
classes: ['versions-downloads-chart'],
184+
id: '-versions-downloads-chart',
185+
attributes: {
186+
'data-widget': 'versions-downloads-chart',
187+
'data-versions-downloads-chart':
188+
_encodeForVersionsChart(weeklyVersionsDownloads)
189+
},
190+
);
191+
}
192+
193+
String _encodeForVersionsChart(WeeklyVersionsDownloadCounts wvcd) {
194+
final date = wvcd.newestDate.toUtc().millisecondsSinceEpoch ~/ 1000;
195+
196+
final allCounts = <int>[];
197+
final allRanges = <String>[];
198+
wvcd.majorRangeWeeklyDownloads.forEach((e) => allCounts.addAll(e.counts));
199+
wvcd.minorRangeWeeklyDownloads.forEach((e) => allCounts.addAll(e.counts));
200+
wvcd.patchRangeWeeklyDownloads.forEach((e) => allCounts.addAll(e.counts));
201+
allCounts.addAll(wvcd.totalWeeklyDownloads);
202+
203+
wvcd.majorRangeWeeklyDownloads.forEach((e) => allRanges.add(e.versionRange));
204+
wvcd.minorRangeWeeklyDownloads.forEach((e) => allRanges.add(e.versionRange));
205+
wvcd.patchRangeWeeklyDownloads.forEach((e) => allRanges.add(e.versionRange));
206+
207+
return [
208+
encodeIntsAsLittleEndianBase64String([date, ...allCounts]),
209+
allRanges
210+
].join(',');
211+
}
212+
176213
final _statusIconUrls = {
177214
ReportStatus.passed:
178215
staticUrls.getAssetUrl('/static/img/report-ok-icon-green.svg'),

app/lib/scorecard/backend.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import 'package:logging/logging.dart';
1010
import 'package:meta/meta.dart';
1111
import 'package:pool/pool.dart';
1212
import 'package:pub_dev/service/download_counts/backend.dart';
13+
import 'package:pub_dev/service/download_counts/computations.dart';
1314
import 'package:pub_dev/shared/exceptions.dart';
1415
import 'package:pub_dev/shared/popularity_storage.dart';
1516
import 'package:pub_dev/task/backend.dart';
@@ -162,6 +163,9 @@ class ScoreCardBackend {
162163
taskStatus = PackageVersionStatus.pending;
163164
}
164165

166+
final weeklyVersionsDownloads =
167+
await getWeeklyVersionsDownloads(packageName);
168+
165169
final data = ScoreCardData(
166170
packageName: packageName,
167171
packageVersion: packageVersion,
@@ -173,6 +177,7 @@ class ScoreCardBackend {
173177
),
174178
panaReport: PanaReport.fromSummary(summary, packageStatus: status),
175179
taskStatus: taskStatus,
180+
weeklyVersionsDownloads: weeklyVersionsDownloads,
176181
);
177182
await cacheEntry.set(data);
178183
return data;

app/lib/scorecard/models.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'package:_pub_shared/search/tags.dart';
88
import 'package:json_annotation/json_annotation.dart';
99
import 'package:pana/models.dart';
1010
import 'package:pub_dev/service/download_counts/backend.dart';
11+
import 'package:pub_dev/service/download_counts/download_counts.dart';
1112
import 'package:pub_dev/task/models.dart';
1213

1314
import '../scorecard/backend.dart';
@@ -33,6 +34,7 @@ class ScoreCardData {
3334
final DartdocReport? dartdocReport;
3435
final PanaReport? panaReport;
3536
final PackageVersionStatus? taskStatus;
37+
final WeeklyVersionsDownloadCounts? weeklyVersionsDownloads;
3638

3739
ScoreCardData({
3840
this.packageName,
@@ -42,6 +44,7 @@ class ScoreCardData {
4244
this.dartdocReport,
4345
this.panaReport,
4446
this.taskStatus,
47+
this.weeklyVersionsDownloads,
4548
});
4649

4750
factory ScoreCardData.fromJson(Map<String, dynamic> json) =>

app/lib/scorecard/models.g.dart

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

app/lib/service/download_counts/computations.dart

Lines changed: 66 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -46,42 +46,89 @@ Future<void> upload30DaysTotal(Map<String, int> counts) async {
4646
jsonUtf8Encoder.convert(counts));
4747
}
4848

49-
Future<WeeklyDownloadCounts?> getWeeklyDownloads(String package) async {
49+
Future<WeeklyDownloadCounts?> getWeeklyTotalDownloads(String package) async {
5050
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!);
51+
return computeWeeklyTotalDownloads(package);
52+
}));
53+
}
54+
55+
Future<WeeklyVersionsDownloadCounts?> getWeeklyVersionsDownloads(
56+
String package) async {
57+
return (await cache.weeklyVersionsDownloadCounts(package).get(() async {
58+
return computeWeeklyVersionsDownloads(package);
5759
}));
5860
}
5961

60-
/// Computes `weeklyDownloads` starting from `newestDate` for [package].
62+
/// Computes weekly downloads starting from `newestDate` for [package] and 52
63+
/// weeks back.
6164
///
62-
/// Each number in `weeklyDownloads` is the total number of downloads for
65+
/// Each number in weeklyDownloads` is the total number of downloads for
6366
/// a given 7 day period starting from the newest date with download counts
6467
/// data available.
65-
Future<({List<int> weeklyDownloads, DateTime? newestDate})>
66-
computeWeeklyDownloads(String package) async {
67-
final weeklyDownloads = List.filled(52, 0);
68+
Future<WeeklyDownloadCounts?> computeWeeklyTotalDownloads(
69+
String package) async {
6870
final countData =
6971
await downloadCountsBackend.lookupDownloadCountData(package);
7072
if (countData == null) {
71-
return (weeklyDownloads: <int>[], newestDate: null);
73+
return null;
7274
}
7375

74-
final totals = countData.totalCounts;
76+
return WeeklyDownloadCounts(
77+
weeklyDownloads: _computeWeeklyCounts(countData.totalCounts),
78+
newestDate: countData.newestDate!);
79+
}
7580

81+
/// Computes weekly downloads starting from `newestDate` for [package] and 52
82+
/// weeks back for all stored major, minor, and patch version ranges and total
83+
/// downloads.
84+
Future<WeeklyVersionsDownloadCounts?> computeWeeklyVersionsDownloads(
85+
String package) async {
86+
final countData =
87+
await downloadCountsBackend.lookupDownloadCountData(package);
88+
if (countData == null) return null;
89+
90+
final majorRangeWeeklyCounts = <VersionRangeCount>[];
91+
countData.majorRangeCounts.forEach((vrc) {
92+
majorRangeWeeklyCounts.add((
93+
counts: _computeWeeklyCounts(vrc.counts),
94+
versionRange: vrc.versionRange
95+
));
96+
});
97+
final minorRangeWeeklyCounts = <VersionRangeCount>[];
98+
countData.minorRangeCounts.forEach((vrc) {
99+
minorRangeWeeklyCounts.add((
100+
counts: _computeWeeklyCounts(vrc.counts),
101+
versionRange: vrc.versionRange
102+
));
103+
});
104+
final patchRangeWeeklyCounts = <VersionRangeCount>[];
105+
countData.patchRangeCounts.forEach((vrc) {
106+
patchRangeWeeklyCounts.add((
107+
counts: _computeWeeklyCounts(vrc.counts),
108+
versionRange: vrc.versionRange
109+
));
110+
});
111+
112+
final weeklyTotalCounts = _computeWeeklyCounts(countData.totalCounts);
113+
114+
return WeeklyVersionsDownloadCounts(
115+
newestDate: countData.newestDate!,
116+
majorRangeWeeklyDownloads: majorRangeWeeklyCounts,
117+
minorRangeWeeklyDownloads: minorRangeWeeklyCounts,
118+
patchRangeWeeklyDownloads: patchRangeWeeklyCounts,
119+
totalWeeklyDownloads: weeklyTotalCounts);
120+
}
121+
122+
List<int> _computeWeeklyCounts(List<int> dailyCounts) {
123+
final weeklyCounts = List.filled(52, 0);
76124
for (int w = 0; w < 52; w++) {
77125
var sum = 0;
78126
for (int d = 0; d < 7; d++) {
79-
if (totals[w * 7 + d] > 0) {
80-
sum += totals[w * 7 + d];
127+
if (dailyCounts[w * 7 + d] > 0) {
128+
sum += dailyCounts[w * 7 + d];
81129
}
82130
}
83-
weeklyDownloads[w] = sum;
131+
weeklyCounts[w] = sum;
84132
}
85-
86-
return (weeklyDownloads: weeklyDownloads, newestDate: countData.newestDate!);
133+
return weeklyCounts;
87134
}

app/lib/service/download_counts/download_counts.dart

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,19 @@ import 'package:json_annotation/json_annotation.dart';
88
import 'package:pub_semver/pub_semver.dart';
99
part 'download_counts.g.dart';
1010

11-
/// A tuple containing a string describing a `versionRange`, for instance
12-
/// '>=1.0.0-0 <2.0.0', and a list of integers with a count for each day.
13-
/// The `counts` list contains at most [maxAge] entries. The first entry
14-
/// represents the number of downloads on `newestDate` followed by the
15-
/// downloads on `newestDate` - 1 and so on. E.g.
11+
/// A [VersionRangeCount] is a tuple containing a version range and a list of
12+
/// download counts for periods of same length.
13+
///
14+
/// The first entry in the tuple is a string describing the `versionRange`, for
15+
/// instance '>=1.0.0-0 <2.0.0'.
16+
///
17+
/// The second entry in the tuple is an integer list of `counts` with download
18+
/// counts for each period. A period could for instance be a day, or a week etc.
19+
/// The `counts` list contains at most [maxAge] entries.
20+
///
21+
/// Consider the example of period being one day. The first count represents the
22+
/// number of downloads on `newestDate` followed by the downloads on
23+
/// `newestDate` - 1 and so on. E.g.
1624
///
1725
/// counts = [ 42, 21, 55 ]
1826
/// ▲ ▲ ▲
@@ -218,7 +226,11 @@ class CountData {
218226

219227
@JsonSerializable(includeIfNull: false)
220228
class WeeklyDownloadCounts {
229+
/// Each number in [weeklyDownloads] is the total number of downloads for
230+
/// a given 7 day period starting from [newestDate].
221231
final List<int> weeklyDownloads;
232+
233+
/// The newest date with download counts data available.
222234
final DateTime newestDate;
223235

224236
WeeklyDownloadCounts({
@@ -230,3 +242,46 @@ class WeeklyDownloadCounts {
230242
_$WeeklyDownloadCountsFromJson(json);
231243
Map<String, dynamic> toJson() => _$WeeklyDownloadCountsToJson(this);
232244
}
245+
246+
@JsonSerializable(includeIfNull: false)
247+
class WeeklyVersionsDownloadCounts {
248+
/// An integer list where each number is the total number of downloads for a
249+
/// given 7 day period starting from [newestDate].
250+
final List<int> totalWeeklyDownloads;
251+
252+
/// A list of [VersionRangeCount] with major version ranges and weekly
253+
/// downloads for these ranges.
254+
///
255+
/// E.g. each number in the `counts` list is the total number of downloads for
256+
/// the range in a 7 day period starting from [newestDate].
257+
final List<VersionRangeCount> majorRangeWeeklyDownloads;
258+
259+
/// A list of [VersionRangeCount] with minor version ranges and weekly
260+
/// downloads for these ranges.
261+
///
262+
/// E.g. each number in the `counts` list is the total number of downloads for
263+
/// the range in a 7 day period starting from [newestDate].
264+
final List<VersionRangeCount> minorRangeWeeklyDownloads;
265+
266+
/// A list of [VersionRangeCount] with patch version ranges and weekly
267+
/// downloads for these ranges.
268+
///
269+
/// E.g. each number in the `counts` list is the total number of downloads for
270+
/// the range in a 7 day period starting from [newestDate].
271+
final List<VersionRangeCount> patchRangeWeeklyDownloads;
272+
273+
/// The newest date with download counts data available.
274+
final DateTime newestDate;
275+
276+
WeeklyVersionsDownloadCounts({
277+
required this.newestDate,
278+
required this.majorRangeWeeklyDownloads,
279+
required this.minorRangeWeeklyDownloads,
280+
required this.patchRangeWeeklyDownloads,
281+
required this.totalWeeklyDownloads,
282+
});
283+
284+
factory WeeklyVersionsDownloadCounts.fromJson(Map<String, dynamic> json) =>
285+
_$WeeklyVersionsDownloadCountsFromJson(json);
286+
Map<String, dynamic> toJson() => _$WeeklyVersionsDownloadCountsToJson(this);
287+
}

0 commit comments

Comments
 (0)