Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
27 changes: 6 additions & 21 deletions app/lib/frontend/templates/views/pkg/score_tab.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:convert';

import 'package:_pub_shared/data/download_counts_data.dart';
import 'package:_pub_shared/format/encoding.dart';
import 'package:_pub_shared/format/number_format.dart';
import 'package:pana/models.dart';
import 'package:pub_dev/service/download_counts/download_counts.dart';
import 'package:pub_dev/shared/popularity_storage.dart';
import 'package:pub_dev/shared/utils.dart';

import '../../../../scorecard/models.dart' hide ReportStatus;
import '../../../../shared/urls.dart' as urls;
Expand Down Expand Up @@ -184,31 +188,12 @@ d.Node _downloadsChart(WeeklyVersionDownloadCounts weeklyVersionDownloads) {
id: '-downloads-chart',
attributes: {
'data-widget': 'downloads-chart',
'data-downloads-chart': _encodeForDownloadsChart(weeklyVersionDownloads)
'data-downloads-chart-points':
base64Encode(jsonUtf8Encoder.convert(weeklyVersionDownloads))
},
);
}

String _encodeForDownloadsChart(WeeklyVersionDownloadCounts wvcd) {
final date = wvcd.newestDate.toUtc().millisecondsSinceEpoch ~/ 1000;

final allCounts = <int>[];
final allRanges = <String>[];
wvcd.majorRangeWeeklyDownloads.forEach((e) => allCounts.addAll(e.counts));
wvcd.minorRangeWeeklyDownloads.forEach((e) => allCounts.addAll(e.counts));
wvcd.patchRangeWeeklyDownloads.forEach((e) => allCounts.addAll(e.counts));
allCounts.addAll(wvcd.totalWeeklyDownloads);

wvcd.majorRangeWeeklyDownloads.forEach((e) => allRanges.add(e.versionRange));
wvcd.minorRangeWeeklyDownloads.forEach((e) => allRanges.add(e.versionRange));
wvcd.patchRangeWeeklyDownloads.forEach((e) => allRanges.add(e.versionRange));

return [
encodeIntsAsLittleEndianBase64String([date, ...allCounts]),
allRanges
].join(',');
}

final _statusIconUrls = {
ReportStatus.passed:
staticUrls.getAssetUrl('/static/img/report-ok-icon-green.svg'),
Expand Down
1 change: 1 addition & 0 deletions app/lib/scorecard/models.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import 'dart:io';

import 'package:_pub_shared/data/download_counts_data.dart';
import 'package:_pub_shared/search/tags.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:pana/models.dart';
Expand Down
1 change: 1 addition & 0 deletions app/lib/service/download_counts/computations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import 'dart:async';
import 'dart:math';

import 'package:_pub_shared/data/download_counts_data.dart';
import 'package:gcloud/storage.dart';
import 'package:pub_dev/service/download_counts/backend.dart';
import 'package:pub_dev/service/download_counts/download_counts.dart';
Expand Down
71 changes: 1 addition & 70 deletions app/lib/service/download_counts/download_counts.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,13 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:_pub_shared/data/download_counts_data.dart';
import 'package:basics/basics.dart';
import 'package:collection/collection.dart' show IterableExtension;
import 'package:json_annotation/json_annotation.dart';
import 'package:pub_semver/pub_semver.dart';
part 'download_counts.g.dart';

/// A [VersionRangeCount] is a tuple containing a version range and a list of
/// download counts for periods of same length.
///
/// The first entry in the tuple is a string describing the `versionRange`, for
/// instance '>=1.0.0-0 <2.0.0'.
///
/// The second entry in the tuple is an integer list of `counts` with download
/// counts for each period. A period could for instance be a day, or a week etc.
/// The `counts` list contains at most [maxAge] entries.
///
/// Consider the example of period being one day. The first count represents the
/// number of downloads on `newestDate` followed by the downloads on
/// `newestDate` - 1 and so on. E.g.
///
/// counts = [ 42, 21, 55 ]
/// ▲ ▲ ▲
/// │ │ └──────────── Download count on newestDate - 2 days
/// │ │
/// │ └──────────────── Download count on newestDate - 1 day
/// │
/// └──────────────────── Download count on newestDate
///
///
/// Each entry in the `counts` list is non-negativ. A `0` entry can for a given
/// day mean that the version range has no downloads or that there is no data.
typedef VersionRangeCount = ({String versionRange, List<int> counts});

/// The maximum number of days for which we store downloads counts for a package.
const maxAge = 731;

Expand Down Expand Up @@ -242,46 +216,3 @@ class WeeklyDownloadCounts {
_$WeeklyDownloadCountsFromJson(json);
Map<String, dynamic> toJson() => _$WeeklyDownloadCountsToJson(this);
}

@JsonSerializable(includeIfNull: false)
class WeeklyVersionDownloadCounts {
/// An integer list where each number is the total number of downloads for a
/// given 7 day period starting from [newestDate].
final List<int> totalWeeklyDownloads;

/// A list of [VersionRangeCount] with major version ranges and weekly
/// downloads for these ranges.
///
/// E.g. each number in the `counts` list is the total number of downloads for
/// the range in a 7 day period starting from [newestDate].
final List<VersionRangeCount> majorRangeWeeklyDownloads;

/// A list of [VersionRangeCount] with minor version ranges and weekly
/// downloads for these ranges.
///
/// E.g. each number in the `counts` list is the total number of downloads for
/// the range in a 7 day period starting from [newestDate].
final List<VersionRangeCount> minorRangeWeeklyDownloads;

/// A list of [VersionRangeCount] with patch version ranges and weekly
/// downloads for these ranges.
///
/// E.g. each number in the `counts` list is the total number of downloads for
/// the range in a 7 day period starting from [newestDate].
final List<VersionRangeCount> patchRangeWeeklyDownloads;

/// The newest date with download counts data available.
final DateTime newestDate;

WeeklyVersionDownloadCounts({
required this.newestDate,
required this.majorRangeWeeklyDownloads,
required this.minorRangeWeeklyDownloads,
required this.patchRangeWeeklyDownloads,
required this.totalWeeklyDownloads,
});

factory WeeklyVersionDownloadCounts.fromJson(Map<String, dynamic> json) =>
_$WeeklyVersionDownloadCountsFromJson(json);
Map<String, dynamic> toJson() => _$WeeklyVersionDownloadCountsToJson(this);
}
1 change: 1 addition & 0 deletions app/lib/shared/redis_cache.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'dart:async';
import 'dart:convert';
import 'dart:math';

import 'package:_pub_shared/data/download_counts_data.dart';
import 'package:_pub_shared/data/package_api.dart' show VersionScore;
import 'package:gcloud/service_scope.dart' as ss;
import 'package:googleapis/youtube/v3.dart';
Expand Down
1 change: 1 addition & 0 deletions pkg/_pub_shared/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ targets:
- 'lib/data/admin_api.dart'
- 'lib/data/advisories_api.dart'
- 'lib/data/completion.dart'
- 'lib/data/download_counts_data.dart'
- 'lib/data/package_api.dart'
- 'lib/data/page_data.dart'
- 'lib/data/publisher_api.dart'
Expand Down
76 changes: 76 additions & 0 deletions pkg/_pub_shared/lib/data/download_counts_data.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:json_annotation/json_annotation.dart';

part 'download_counts_data.g.dart';

/// A [VersionRangeCount] is a tuple containing a version range and a list of
/// download counts for periods of same length.
///
/// The first entry in the tuple is a string describing the `versionRange`, for
/// instance '>=1.0.0-0 <2.0.0'.
///
/// The second entry in the tuple is an integer list of `counts` with download
/// counts for each period. A period could for instance be a day, or a week etc.
///
/// Consider the example of period being one day. The first count represents the
/// number of downloads on `newestDate` followed by the downloads on
/// `newestDate` - 1 and so on. E.g.
///
/// counts = [ 42, 21, 55 ]
/// ▲ ▲ ▲
/// │ │ └──────────── Download count on newestDate - 2 days
/// │ │
/// │ └──────────────── Download count on newestDate - 1 day
/// │
/// └──────────────────── Download count on newestDate
///
///
/// Each entry in the `counts` list is non-negativ. A `0` entry can for a given
/// day mean that the version range has no downloads or that there is no data.
typedef VersionRangeCount = ({String versionRange, List<int> counts});

@JsonSerializable(includeIfNull: false)
class WeeklyVersionDownloadCounts {
/// An integer list where each number is the total number of downloads for a
/// given 7 day period starting from [newestDate].
final List<int> totalWeeklyDownloads;

/// A list of [VersionRangeCount] with major version ranges and weekly
/// downloads for these ranges.
///
/// E.g. each number in the `counts` list is the total number of downloads for
/// the range in a 7 day period starting from [newestDate].
final List<VersionRangeCount> majorRangeWeeklyDownloads;

/// A list of [VersionRangeCount] with minor version ranges and weekly
/// downloads for these ranges.
///
/// E.g. each number in the `counts` list is the total number of downloads for
/// the range in a 7 day period starting from [newestDate].
final List<VersionRangeCount> minorRangeWeeklyDownloads;

/// A list of [VersionRangeCount] with patch version ranges and weekly
/// downloads for these ranges.
///
/// E.g. each number in the `counts` list is the total number of downloads for
/// the range in a 7 day period starting from [newestDate].
final List<VersionRangeCount> patchRangeWeeklyDownloads;

/// The newest date with download counts data available.
final DateTime newestDate;

WeeklyVersionDownloadCounts({
required this.newestDate,
required this.majorRangeWeeklyDownloads,
required this.minorRangeWeeklyDownloads,
required this.patchRangeWeeklyDownloads,
required this.totalWeeklyDownloads,
});

factory WeeklyVersionDownloadCounts.fromJson(Map<String, dynamic> json) =>
_$WeeklyVersionDownloadCountsFromJson(json);
Map<String, dynamic> toJson() => _$WeeklyVersionDownloadCountsToJson(this);
}
83 changes: 83 additions & 0 deletions pkg/_pub_shared/lib/data/download_counts_data.g.dart

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

21 changes: 21 additions & 0 deletions pkg/web_app/lib/src/widget/downloads_chart/widget.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:convert';

import 'package:_pub_shared/data/download_counts_data.dart';
import 'package:web/web.dart';

void create(HTMLElement element, Map<String, String> options) {
final dataPoints = options['points'];
if (dataPoints == null) {
throw UnsupportedError('data-downloads-chart-points required');
}

final svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
element.append(svg);
final data = WeeklyVersionDownloadCounts.fromJson((utf8.decoder
.fuse(json.decoder)
.convert(base64Decode(dataPoints)) as Map<String, dynamic>));
}
2 changes: 2 additions & 0 deletions pkg/web_app/lib/src/widget/widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import '../web_util.dart';
import 'completion/widget.dart' deferred as completion;
import 'switch/widget.dart' as switch_;
import 'weekly_sparkline/widget.dart' as weekly_sparkline;
import 'downloads_chart/widget.dart' as downloads_chart;

/// Function to create an instance of the widget given an element and options.
///
Expand All @@ -35,6 +36,7 @@ final _widgets = <String, _WidgetLoaderFn>{
'completion': () => completion.loadLibrary().then((_) => completion.create),
'switch': () => switch_.create,
'weekly-sparkline': () => weekly_sparkline.create,
'downloads-chart': () => downloads_chart.create,
};

Future<_WidgetFn> _noSuchWidget() async =>
Expand Down
Loading