Skip to content

Commit 209677d

Browse files
committed
Maintainer wanted model, badge, action and admin UI.
1 parent ee285d4 commit 209677d

File tree

20 files changed

+173
-4
lines changed

20 files changed

+173
-4
lines changed

app/lib/frontend/templates/package.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ d.Node renderPkgHeader(PackagePageData data) {
116116
packageName: package.name!,
117117
publisherId: package.publisherId,
118118
published: data.version.created!,
119+
isMaintainerWanted: package.isMaintainerWanted ?? false,
119120
isNullSafe: isNullSafe,
120121
isDart3Compatible:
121122
pkgView.tags.contains(PackageVersionTags.isDart3Compatible),

app/lib/frontend/templates/package_misc.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ final nameMatchBadgeNode = packageBadgeNode(
3535
color: 'name-match',
3636
);
3737

38+
/// Renders the maintainer-wanted badged used by package listing and package page.
39+
final maintainerWantedBadgeNode = packageBadgeNode(label: 'Maintainer wanted');
40+
3841
/// Renders the null-safe badge used by package listing and package page.
3942
d.Node nullSafeBadgeNode({String? title}) {
4043
return packageBadgeNode(

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,19 @@ d.Node packageAdminPageNode({
200200
),
201201
),
202202
],
203+
d.a(name: 'maintainer-wanted'),
204+
d.h3(text: 'Maintainer wanted'),
205+
d.markdown(
206+
'A package that\'s marked as *maintainWanted* will be featured with an '
207+
'extra badge on the package page and in the search results.'),
208+
d.div(
209+
classes: ['-pub-form-checkbox-row'],
210+
child: material.checkbox(
211+
id: '-admin-is-maintainer-wanted-checkbox',
212+
label: 'Mark "maintainWanted"',
213+
checked: package.isMaintainerWanted ?? false,
214+
),
215+
),
203216
_automatedPublishing(package),
204217
d.a(name: 'version-retraction'),
205218
d.h2(text: 'Version retraction'),

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ d.Node packageHeaderNode({
1212
required String packageName,
1313
required String? publisherId,
1414
required DateTime published,
15+
required bool isMaintainerWanted,
1516
required bool isNullSafe,
1617
required bool isDart3Compatible,
1718
required bool isDart3Incompatible,
@@ -22,6 +23,7 @@ d.Node packageHeaderNode({
2223
d.span(child: d.xAgoTimestamp(published)),
2324
d.text(' '),
2425
if (publisherId != null) ..._publisher(publisherId),
26+
if (isMaintainerWanted) maintainerWantedBadgeNode,
2527
if (isNullSafe && !isDart3Compatible) nullSafeBadgeNode(),
2628
if (isDart3Compatible) dart3CompatibleNode,
2729
if (isDart3Incompatible) dart3IncompatibleNode,

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ d.Node _packageItem(
7979
required bool isNameMatch,
8080
}) {
8181
final isFlutterFavorite = view.tags.contains(PackageTags.isFlutterFavorite);
82+
final isMaintainerWanted = view.tags.contains(PackageTags.isMaintainerWanted);
8283
final isNullSafe = view.tags.contains(PackageVersionTags.isNullSafe);
8384
final isDart3Compatible =
8485
view.tags.contains(PackageVersionTags.isDart3Compatible);
@@ -139,6 +140,7 @@ d.Node _packageItem(
139140
child: licenseNode,
140141
),
141142
if (isFlutterFavorite) flutterFavoriteBadgeNode,
143+
if (isMaintainerWanted) maintainerWantedBadgeNode,
142144
if (isNullSafe && !isDart3Compatible) nullSafeBadgeNode(),
143145
if (isDart3Compatible) dart3CompatibleNode,
144146
if (isDart3Incompatible) dart3IncompatibleNode,

app/lib/package/backend.dart

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,7 @@ class PackageBackend {
419419
isDiscontinued: p.isDiscontinued,
420420
replacedBy: p.replacedBy,
421421
isUnlisted: p.isUnlisted,
422+
isMaintainerWanted: p.isMaintainerWanted ?? false,
422423
);
423424
}
424425

@@ -463,6 +464,13 @@ class PackageBackend {
463464
p.isUnlisted = options.isUnlisted!;
464465
optionsChanges.add('unlisted');
465466
}
467+
if ((options.isMaintainerWanted ?? false) &&
468+
(options.isMaintainerWanted ?? false) !=
469+
(p.isMaintainerWanted ?? false)) {
470+
p.updateMaintainerWanted(
471+
isMaintainerWanted: options.isMaintainerWanted ?? false);
472+
optionsChanges.add('maintainerWanted');
473+
}
466474

467475
if (optionsChanges.isEmpty) {
468476
return;
@@ -471,7 +479,8 @@ class PackageBackend {
471479
p.updated = clock.now().toUtc();
472480
_logger.info('Updating $package options: '
473481
'isDiscontinued: ${p.isDiscontinued} '
474-
'isUnlisted: ${p.isUnlisted}');
482+
'isUnlisted: ${p.isUnlisted} '
483+
'isMaintainerWanted: ${p.isMaintainerWanted}');
475484
tx.insert(p);
476485
tx.insert(await AuditLogRecord.packageOptionsUpdated(
477486
agent: authenticatedUser,

app/lib/package/models.dart

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,16 @@ class Package extends db.ExpandoModel<String> {
137137
@db.DateTimeProperty()
138138
DateTime? adminDeletedAt;
139139

140+
/// `true` if a package admin wants to advertise that they are looking for new maintainer(s).
141+
///
142+
/// Note: the value expires and resets to false after a set period (e.g. 6 months after setting it).
143+
@db.BoolProperty(required: false)
144+
bool? isMaintainerWanted;
145+
146+
/// The timestamp when the [isMaintainerWanted] flag was set.
147+
@db.DateTimeProperty()
148+
DateTime? maintainerWantedStartedAt;
149+
140150
/// Tags that are assigned to this package.
141151
///
142152
/// The permissions required to assign a tag typically depends on the tag.
@@ -188,6 +198,7 @@ class Package extends db.ExpandoModel<String> {
188198
..isUnlisted = false
189199
..isModerated = false
190200
..isAdminDeleted = false
201+
..isMaintainerWanted = false
191202
..assignedTags = []
192203
..deletedVersions = [];
193204
}
@@ -380,6 +391,7 @@ class Package extends db.ExpandoModel<String> {
380391
],
381392
if (isUnlisted) PackageTags.isUnlisted,
382393
if (publisherId != null) PackageTags.publisherTag(publisherId!),
394+
if (isMaintainerWanted ?? false) PackageTags.isMaintainerWanted,
383395
};
384396
}
385397

@@ -419,6 +431,14 @@ class Package extends db.ExpandoModel<String> {
419431
adminDeletedAt = isAdminDeleted ? clock.now().toUtc() : null;
420432
updated = clock.now().toUtc();
421433
}
434+
435+
void updateMaintainerWanted({
436+
required bool isMaintainerWanted,
437+
}) {
438+
this.isMaintainerWanted = isMaintainerWanted;
439+
maintainerWantedStartedAt = isMaintainerWanted ? clock.now().toUtc() : null;
440+
updated = clock.now().toUtc();
441+
}
422442
}
423443

424444
/// Describes the various categories of latest releases.

app/lib/shared/integrity.dart

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,11 @@ class IntegrityChecker {
449449
isAdminDeleted: p.isAdminDeleted,
450450
adminDeletedAt: p.adminDeletedAt,
451451
);
452+
yield* _checkMaintainerWantedFlags(
453+
package: p.name!,
454+
isMaintainerWanted: p.isMaintainerWanted,
455+
maintainerWantedStartedAt: p.maintainerWantedStartedAt,
456+
);
452457
if (p.isModerated) {
453458
_packagesWithIsModeratedFlag.add(p.name!);
454459
}
@@ -1056,3 +1061,18 @@ Stream<String> _checkAdminDeletedFlags({
10561061
yield '$kind "$id" has `isAdminDeleted = false` but `adminDeletedAt` is not null.';
10571062
}
10581063
}
1064+
1065+
/// Check that `isMaintainerWanted` and `maintainerWantedStartedAt` are consistent.
1066+
Stream<String> _checkMaintainerWantedFlags({
1067+
required String package,
1068+
required bool? isMaintainerWanted,
1069+
required DateTime? maintainerWantedStartedAt,
1070+
}) async* {
1071+
isMaintainerWanted ??= false;
1072+
if (isMaintainerWanted && maintainerWantedStartedAt == null) {
1073+
yield 'Package "$package" has `isMaintainerWanted = true` but `maintainerWantedStartedAt` is null.';
1074+
}
1075+
if (!isMaintainerWanted && maintainerWantedStartedAt != null) {
1076+
yield 'Package "$package" has `isMaintainerWanted = false` but `maintainerWantedStartedAt` is not null.';
1077+
}
1078+
}

app/lib/tool/test_profile/importer.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ Future<void> importProfile({
161161
isDiscontinued: testPackage.isDiscontinued,
162162
replacedBy: testPackage.replacedBy,
163163
isUnlisted: testPackage.isUnlisted,
164+
isMaintainerWanted: testPackage.isMaintainerWanted,
164165
));
165166

166167
if (testPackage.retractedVersions != null) {

app/lib/tool/test_profile/models.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ class TestPackage {
8080
final bool? isDiscontinued;
8181
final String? replacedBy;
8282
final bool? isUnlisted;
83+
final bool? isMaintainerWanted;
8384
final bool? isFlutterFavorite;
8485
final List<String>? retractedVersions;
8586
final int? likeCount;
@@ -92,6 +93,7 @@ class TestPackage {
9293
this.isDiscontinued,
9394
this.replacedBy,
9495
this.isUnlisted,
96+
this.isMaintainerWanted,
9597
this.isFlutterFavorite,
9698
this.retractedVersions,
9799
this.likeCount,

0 commit comments

Comments
 (0)