Skip to content

Commit b4309ae

Browse files
authored
Notify uploader(s) when remaining version count is less than 100. (#8441)
1 parent 1b8c714 commit b4309ae

File tree

5 files changed

+76
-36
lines changed

5 files changed

+76
-36
lines changed

app/lib/frontend/handlers/pubapi.dart

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -154,14 +154,9 @@ class PubApi {
154154
@EndPoint.get('/api/packages/versions/newUploadFinish/<uploadId>')
155155
Future<SuccessMessage> finishPackageUpload(
156156
Request request, String uploadId) async {
157-
final pv = await packageBackend.publishUploadedBlob(uploadId);
157+
final messages = await packageBackend.publishUploadedBlob(uploadId);
158158
return SuccessMessage(
159-
success: Message(
160-
message: 'Successfully uploaded '
161-
'${urls.pkgPageUrl(pv.package, includeHost: true)} '
162-
'version ${pv.version}, '
163-
'it may take up-to 10 minutes before the new version is available.',
164-
),
159+
success: Message(message: messages.join('\n')),
165160
);
166161
}
167162

app/lib/package/backend.dart

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -882,8 +882,9 @@ class PackageBackend {
882882
);
883883
}
884884

885-
/// Finishes the upload of a package.
886-
Future<PackageVersion> publishUploadedBlob(String guid) async {
885+
/// Finishes the upload of a package and returns the list of messages
886+
/// related to the publishing.
887+
Future<List<String>> publishUploadedBlob(String guid) async {
887888
final restriction = await getUploadRestrictionStatus();
888889
if (restriction == UploadRestrictionStatus.noUploads) {
889890
throw PackageRejectedException.uploadRestricted();
@@ -984,7 +985,7 @@ class PackageBackend {
984985
sw.reset();
985986
final entities = await _createUploadEntities(db, agent, archive,
986987
sha256Hash: sha256Hash);
987-
final version = await _performTarballUpload(
988+
final (version, uploadMessages) = await _performTarballUpload(
988989
entities: entities,
989990
agent: agent,
990991
archive: archive,
@@ -998,7 +999,13 @@ class PackageBackend {
998999
sw.reset();
9991000
await _incomingBucket.deleteWithRetry(tmpObjectName(guid));
10001001
_logger.info('Temporary object removed in ${sw.elapsed}.');
1001-
return version;
1002+
return [
1003+
'Successfully uploaded '
1004+
'${urls.pkgPageUrl(version.package, includeHost: true)} '
1005+
'version ${version.version}, '
1006+
'it may take up-to 10 minutes before the new version is available.',
1007+
...uploadMessages,
1008+
];
10021009
});
10031010
}
10041011

@@ -1057,14 +1064,15 @@ class PackageBackend {
10571064
}
10581065
}
10591066

1060-
Future<PackageVersion> _performTarballUpload({
1067+
Future<(PackageVersion, List<String>)> _performTarballUpload({
10611068
required _UploadEntities entities,
10621069
required AuthenticatedAgent agent,
10631070
required PackageSummary archive,
10641071
required String guid,
10651072
required bool hasCanonicalArchiveObject,
10661073
}) async {
10671074
final sw = Stopwatch()..start();
1075+
final uploadMessages = <String>[];
10681076
final newVersion = entities.packageVersion;
10691077
final [currentDartSdk, currentFlutterSdk] = await Future.wait([
10701078
getCachedDartSdkVersion(lastKnownStable: toolStableDartSdkVersion),
@@ -1100,14 +1108,6 @@ class PackageBackend {
11001108
package: newVersion.package,
11011109
isNew: isNew,
11021110
);
1103-
final email = createPackageUploadedEmail(
1104-
packageName: newVersion.package,
1105-
packageVersion: newVersion.version!,
1106-
displayId: agent.displayId,
1107-
authorizedUploaders:
1108-
uploaderEmails.map((email) => EmailAddress(email)).toList(),
1109-
);
1110-
final outgoingEmail = emailBackend.prepareEntity(email);
11111111
Package? package;
11121112
final existingVersions = await db
11131113
.query<PackageVersion>(ancestorKey: newVersion.packageKey!)
@@ -1116,7 +1116,7 @@ class PackageBackend {
11161116

11171117
// Add the new package to the repository by storing the tarball and
11181118
// inserting metadata to datastore (which happens atomically).
1119-
final pv = await withRetryTransaction(db, (tx) async {
1119+
final (pv, outgoingEmail) = await withRetryTransaction(db, (tx) async {
11201120
_logger.info('Starting datastore transaction.');
11211121

11221122
final tuple = (await tx.lookup([
@@ -1156,10 +1156,21 @@ class PackageBackend {
11561156

11571157
final maxVersionCount = maxVersionsPerPackageOverrides[package!.name] ??
11581158
maxVersionsPerPackage;
1159-
if (package!.versionCount >= maxVersionCount) {
1159+
final remainingVersionCount = maxVersionCount - package!.versionCount;
1160+
if (remainingVersionCount <= 0) {
11601161
throw PackageRejectedException.maxVersionCountReached(
11611162
newVersion.package, maxVersionCount);
11621163
}
1164+
if (remainingVersionCount <= 100) {
1165+
// We need to decrease the remaining version count as the newly uploaded
1166+
// version is not yet in it.
1167+
final limitAfterUpload = remainingVersionCount - 1;
1168+
final s = limitAfterUpload == 1 ? '' : 's';
1169+
uploadMessages.add(
1170+
'The package "${package!.name!}" has $limitAfterUpload version$s left '
1171+
'before reaching the limit of $maxVersionCount. '
1172+
'Please contact [email protected]');
1173+
}
11631174

11641175
if (package!.deletedVersions != null &&
11651176
package!.deletedVersions!.contains(newVersion.version!)) {
@@ -1196,6 +1207,16 @@ class PackageBackend {
11961207
await tarballStorage.copyArchiveFromCanonicalToPublicBucket(
11971208
newVersion.package, newVersion.version!);
11981209

1210+
final email = createPackageUploadedEmail(
1211+
packageName: newVersion.package,
1212+
packageVersion: newVersion.version!,
1213+
displayId: agent.displayId,
1214+
authorizedUploaders:
1215+
uploaderEmails.map((email) => EmailAddress(email)).toList(),
1216+
uploadMessages: uploadMessages,
1217+
);
1218+
final outgoingEmail = emailBackend.prepareEntity(email);
1219+
11991220
final inserts = <Model>[
12001221
package!,
12011222
newVersion,
@@ -1221,7 +1242,7 @@ class PackageBackend {
12211242

12221243
_logger.info('Trying to commit datastore changes.');
12231244
tx.queueMutations(inserts: inserts);
1224-
return newVersion;
1245+
return (newVersion, outgoingEmail);
12251246
});
12261247
_logger.info('Upload successful. [package-uploaded]');
12271248
_logger.info('Upload transaction completed in ${sw.elapsed}.');
@@ -1237,7 +1258,7 @@ class PackageBackend {
12371258
.addAsyncFn(() => _postUploadTasks(package, newVersion, outgoingEmail));
12381259

12391260
_logger.info('Post-upload tasks completed in ${sw.elapsed}.');
1240-
return pv;
1261+
return (pv, uploadMessages);
12411262
}
12421263

12431264
/// The post-upload tasks are not critical and could fail without any impact on

app/lib/service/email/email_templates.dart

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -204,21 +204,21 @@ EmailMessage createPackageUploadedEmail({
204204
required String packageVersion,
205205
required String displayId,
206206
required List<EmailAddress> authorizedUploaders,
207+
required List<String> uploadMessages,
207208
}) {
208209
final url =
209210
pkgPageUrl(packageName, version: packageVersion, includeHost: true);
210211
final subject = 'Package uploaded: $packageName $packageVersion';
211-
final bodyText = '''Dear package maintainer,
212-
213-
$displayId has published a new version ($packageVersion) of the $packageName package to the Dart package site ($primaryHost).
214-
215-
For details, go to $url
216-
217-
${_footer('package')}
218-
''';
219-
220-
return EmailMessage(
221-
_notificationsFrom, authorizedUploaders, subject, bodyText);
212+
final paragraphs = [
213+
'Dear package maintainer,',
214+
'$displayId has published a new version ($packageVersion) of the $packageName package to the Dart package site ($primaryHost).',
215+
'For details, go to $url',
216+
...uploadMessages,
217+
_footer('package'),
218+
];
219+
220+
return EmailMessage(_notificationsFrom, authorizedUploaders, subject,
221+
paragraphs.join('\n\n'));
222222
}
223223

224224
/// Creates the [EmailMessage] that will be sent to users about new invitations

app/test/package/upload_test.dart

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1238,7 +1238,30 @@ void main() {
12381238
],
12391239
),
12401240
fn: () async {
1241-
packageBackend.maxVersionsPerPackage = 100;
1241+
packageBackend.maxVersionsPerPackage = 102;
1242+
1243+
final tarball101 = await packageArchiveBytes(
1244+
pubspecContent: generatePubspecYaml('busy_pkg', '1.0.101'));
1245+
final rs101 = await createPubApiClient(authToken: adminClientToken)
1246+
.uploadPackageBytes(tarball101);
1247+
expect(
1248+
rs101.success.message,
1249+
contains(
1250+
'The package "busy_pkg" has 1 version left before reaching the limit of 102. '
1251+
'Please contact [email protected]'));
1252+
1253+
final tarball102 = await packageArchiveBytes(
1254+
pubspecContent: generatePubspecYaml('busy_pkg', '1.0.102'));
1255+
final rs102 = await createPubApiClient(authToken: adminClientToken)
1256+
.uploadPackageBytes(tarball102);
1257+
expect(
1258+
rs102.success.message,
1259+
contains(
1260+
'The package "busy_pkg" has 0 versions left before reaching the limit of 102. '
1261+
'Please contact [email protected]'));
1262+
expect(fakeEmailSender.sentMessages.last.bodyText,
1263+
contains('has 0 versions left before reaching the limit'));
1264+
12421265
final tarball = await packageArchiveBytes(
12431266
pubspecContent: generatePubspecYaml('busy_pkg', '2.0.0'));
12441267
final rs = createPubApiClient(authToken: adminClientToken)

app/test/service/email/email_templates_test.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ void main() {
169169
EmailAddress(name: 'Joe', '[email protected]'),
170170
EmailAddress('[email protected]')
171171
],
172+
uploadMessages: [],
172173
);
173174
expect(message.from.toString(), contains('@pub.dev'));
174175
expect(message.recipients.map((e) => e.toString()).toList(), [

0 commit comments

Comments
 (0)