Skip to content

Commit a9568e4

Browse files
authored
Migrate some admin tool to actions. (#8137)
1 parent ba5b411 commit a9568e4

File tree

7 files changed

+152
-93
lines changed

7 files changed

+152
-93
lines changed

app/lib/admin/actions/actions.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ import 'moderation_case_list.dart';
1818
import 'moderation_case_resolve.dart';
1919
import 'moderation_case_update.dart';
2020
import 'moderation_transparency_metrics.dart';
21+
import 'package_discontinue.dart';
2122
import 'package_info.dart';
23+
import 'package_latest_update.dart';
2224
import 'package_reservation_create.dart';
2325
import 'package_reservation_delete.dart';
2426
import 'package_reservation_list.dart';
@@ -102,7 +104,9 @@ final class AdminAction {
102104
moderationCaseResolve,
103105
moderationCaseUpdate,
104106
moderationTransparencyMetrics,
107+
packageDiscontinue,
105108
packageInfo,
109+
packageLatestUpdate,
106110
packageReservationCreate,
107111
packageReservationDelete,
108112
packageReservationList,
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:clock/clock.dart';
6+
import 'package:pub_dev/package/backend.dart';
7+
import 'package:pub_dev/package/models.dart';
8+
import 'package:pub_dev/shared/datastore.dart';
9+
10+
import 'actions.dart';
11+
12+
final packageDiscontinue = AdminAction(
13+
name: 'package-discontinue',
14+
summary: 'Sets the package discontinued.',
15+
description: '''
16+
Sets the `Package.isDiscontinued` and `Package.replacedBy` properties.
17+
''',
18+
options: {
19+
'package': 'The package to be discontinued.',
20+
'value': 'The value to set (defaults to true).',
21+
'replaced-by':
22+
'The Package.replacedBy field (if not set will be set to `null`).',
23+
},
24+
invoke: (options) async {
25+
final package = options['package'];
26+
InvalidInputException.check(
27+
package != null && package.isNotEmpty,
28+
'`package` must be given',
29+
);
30+
final value = options['value'] ?? 'true';
31+
InvalidInputException.checkAnyOf(value, 'value', ['true', 'false']);
32+
final valueToSet = value == 'true';
33+
34+
final p = await packageBackend.lookupPackage(package!);
35+
if (p == null) {
36+
throw NotFoundException.resource(package);
37+
}
38+
39+
final replacedBy = options['replaced-by'];
40+
if (replacedBy != null) {
41+
final rp = await packageBackend.lookupPackage(replacedBy);
42+
if (rp == null) {
43+
throw NotFoundException('Replacing package "$replacedBy" not found.');
44+
}
45+
}
46+
47+
final info = await withRetryTransaction(dbService, (tx) async {
48+
final pkg = await tx.lookupOrNull<Package>(p.key);
49+
if (pkg == null) {
50+
throw NotFoundException.resource(package);
51+
}
52+
pkg.isDiscontinued = valueToSet;
53+
pkg.replacedBy = valueToSet ? replacedBy : null;
54+
pkg.updated = clock.now().toUtc();
55+
tx.insert(pkg);
56+
return pkg;
57+
});
58+
59+
return {
60+
'package': {
61+
'name': info.name,
62+
'isDiscontinued': info.isDiscontinued,
63+
'replacedBy': info.replacedBy,
64+
},
65+
};
66+
},
67+
);
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:pub_dev/package/backend.dart';
6+
7+
import 'actions.dart';
8+
9+
final packageLatestUpdate = AdminAction(
10+
name: 'package-latest-update',
11+
summary: 'Updates the latest version of a package or all packages.',
12+
description: '''
13+
Ensures Package.latestVersion / latestPreviewVersion / latestPrereleaseVersion is up-to-date.
14+
15+
When no package is specified, all packages will be updated.
16+
''',
17+
options: {
18+
'package': 'The package to be updated (optional).',
19+
'concurrency':
20+
'The concurrently running update operations (defaults to 10).',
21+
},
22+
invoke: (options) async {
23+
final package = options['package'];
24+
final concurrency = int.parse(options['concurrency'] ?? '10');
25+
26+
if (package != null) {
27+
final updated = await packageBackend.updatePackageVersions(package);
28+
return {
29+
'updated': updated,
30+
};
31+
} else {
32+
final stat = await packageBackend.updateAllPackageVersions(
33+
concurrency: concurrency);
34+
return {
35+
'updatedCount': stat,
36+
};
37+
}
38+
},
39+
);

app/lib/admin/backend.dart

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,11 @@ import 'tools/delete_all_staging.dart';
4444
import 'tools/list_package_blocked.dart';
4545
import 'tools/list_tools.dart';
4646
import 'tools/notify_service.dart';
47-
import 'tools/package_discontinued.dart';
4847
import 'tools/package_publisher.dart';
4948
import 'tools/publisher_member.dart';
5049
import 'tools/recent_uploaders.dart';
5150
import 'tools/set_package_blocked.dart';
5251
import 'tools/set_user_blocked.dart';
53-
import 'tools/update_package_versions.dart';
5452
import 'tools/user_merger.dart';
5553

5654
final _logger = Logger('pub.admin.backend');
@@ -69,9 +67,7 @@ final Map<String, Tool> availableTools = {
6967
'delete-all-staging': executeDeleteAllStaging,
7068
'list-package-blocked': executeListPackageBlocked,
7169
'notify-service': executeNotifyService,
72-
'package-discontinued': executeSetPackageDiscontinued,
7370
'package-publisher': executeSetPackagePublisher,
74-
'update-package-versions': executeUpdatePackageVersions,
7571
'recent-uploaders': executeRecentUploaders,
7672
'publisher-member': executePublisherMember,
7773
'publisher-invite-member': executePublisherInviteMember,

app/lib/admin/tools/package_discontinued.dart

Lines changed: 0 additions & 55 deletions
This file was deleted.

app/lib/admin/tools/update_package_versions.dart

Lines changed: 0 additions & 34 deletions
This file was deleted.

app/test/admin/package_actions_test.dart

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,47 @@ void main() {
2828
}
2929
});
3030
});
31+
32+
testWithProfile('discontinue', fn: () async {
33+
final client = createPubApiClient(authToken: siteAdminToken);
34+
final rs = await client.adminInvokeAction(
35+
'package-discontinue',
36+
AdminInvokeActionArguments(arguments: {
37+
'package': 'oxygen',
38+
'replaced-by': 'neon',
39+
}),
40+
);
41+
expect(rs.output, {
42+
'package': {
43+
'name': 'oxygen',
44+
'isDiscontinued': true,
45+
'replacedBy': 'neon',
46+
},
47+
});
48+
});
49+
50+
testWithProfile('update latest on a single package', fn: () async {
51+
final client = createPubApiClient(authToken: siteAdminToken);
52+
final rs = await client.adminInvokeAction(
53+
'package-latest-update',
54+
AdminInvokeActionArguments(arguments: {
55+
'package': 'oxygen',
56+
}),
57+
);
58+
expect(rs.output, {
59+
'updated': false,
60+
});
61+
});
62+
63+
testWithProfile('update latest on all packages', fn: () async {
64+
final client = createPubApiClient(authToken: siteAdminToken);
65+
final rs = await client.adminInvokeAction(
66+
'package-latest-update',
67+
AdminInvokeActionArguments(arguments: {}),
68+
);
69+
expect(rs.output, {
70+
'updatedCount': 0,
71+
});
72+
});
3173
});
3274
}

0 commit comments

Comments
 (0)