Skip to content

Commit 878d0f2

Browse files
authored
Report unmapped fields in integrity checks + delete Package.isWithheld and withheldReason (#8414)
1 parent 6352f10 commit 878d0f2

File tree

3 files changed

+51
-1
lines changed

3 files changed

+51
-1
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ AppEngine version, listed here to ease deployment and troubleshooting.
44
## Next Release (replace with git tag when deployed)
55
* Bump runtimeVersion to `2024.12.12`.
66
* Upgraded runtime Dart SDK to `3.6.0`.
7+
* Note: Started reporting unmapped fields
8+
* Started and deleting `Package.isWithheld` and `Package.withheldReason`.
79

810
## `20241212t111200-all`
911
* Bump runtimeVersion to `2024.12.11`.

app/lib/shared/integrity.dart

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,22 @@ import 'utils.dart' show canonicalizeVersion, ByteArrayEqualsExt;
3535
final _logger = Logger('integrity.check');
3636
final _random = math.Random.secure();
3737

38+
/// The unmapped/unused fields that we expect to be present on some entities.
39+
/// The presence of such fields won't be reported as integrity issue, only
40+
/// the absent ones will be reported.
41+
const _allowedUnmappedFields = {
42+
'Package.isWithheld',
43+
'Package.withheldReason',
44+
};
45+
3846
/// Checks the integrity of the datastore.
3947
class IntegrityChecker {
4048
final DatastoreDB _db;
4149
final int _concurrency;
4250

51+
/// Maps an unmapped field in the form of `<ClassName>.<fieldName>` to an
52+
/// object identifier (usually the `id` value of the entity).
53+
final _unmappedFieldsToObject = <String, String>{};
4354
final _userToOauth = <String, String?>{};
4455
final _oauthToUser = <String, String>{};
4556
final _deletedUsers = <String>{};
@@ -93,7 +104,13 @@ class IntegrityChecker {
93104
yield* _checkAuditLogs();
94105
yield* _checkModerationCases();
95106
yield* _reportPubspecVersionIssues();
96-
// TODO: report unmapped properties
107+
108+
if (_unmappedFieldsToObject.isNotEmpty) {
109+
for (final entry in _unmappedFieldsToObject.entries) {
110+
if (_allowedUnmappedFields.contains(entry.key)) continue;
111+
yield 'Unmapped field found: "${entry.key}" on entity "${entry.value}".';
112+
}
113+
}
97114
} finally {
98115
_httpClient.close();
99116
}
@@ -448,6 +465,7 @@ class IntegrityChecker {
448465
}
449466

450467
await for (final pvi in pviQuery.run()) {
468+
_updateUnmappedFields(pvi);
451469
final key = pvi.qualifiedVersionKey;
452470
pviKeys.add(key);
453471
yield* checkPackageVersionKey('PackageVersionInfo', key);
@@ -475,6 +493,7 @@ class IntegrityChecker {
475493
..filter('package =', p.name);
476494
final foundAssetIds = <String?>{};
477495
await for (final pva in pvaQuery.run()) {
496+
_updateUnmappedFields(pva);
478497
final key = pva.qualifiedVersionKey;
479498
if (pva.id !=
480499
Uri(pathSegments: [pva.package!, pva.version!, pva.kind!]).path) {
@@ -907,13 +926,24 @@ class IntegrityChecker {
907926
}
908927
}
909928

929+
void _updateUnmappedFields(Model m) {
930+
if (m is ExpandoModel && m.additionalProperties.isNotEmpty) {
931+
for (final key in m.additionalProperties.keys) {
932+
final qualifiedField = [m.runtimeType.toString(), key].join('.');
933+
if (_unmappedFieldsToObject.containsKey(qualifiedField)) continue;
934+
_unmappedFieldsToObject[qualifiedField] = m.id.toString();
935+
}
936+
}
937+
}
938+
910939
Stream<String> _queryWithPool<R extends Model>(
911940
Stream<String> Function(R model) fn) async* {
912941
final query = _db.query<R>();
913942
final pool = Pool(_concurrency);
914943
final futures = <Future<List<String>>>[];
915944
try {
916945
await for (final m in query.run()) {
946+
_updateUnmappedFields(m);
917947
final f = pool.withResource(() => fn(m).toList());
918948
futures.add(f);
919949
}

app/lib/tool/backfill/backfill_new_fields.dart

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import 'package:clock/clock.dart';
66
import 'package:logging/logging.dart';
7+
import 'package:meta/meta.dart';
78
import 'package:pub_dev/account/models.dart';
89
import 'package:pub_dev/package/api_export/api_exporter.dart';
910
import 'package:pub_dev/package/backend.dart';
@@ -20,9 +21,11 @@ final _logger = Logger('backfill_new_fields');
2021
/// release could remove the backfill from here.
2122
Future<void> backfillNewFields() async {
2223
await migrateIsBlocked();
24+
await _removeKnownUnmappedFields();
2325
}
2426

2527
/// Migrates entities from the `isBlocked` fields to the new `isModerated` instead.
28+
@visibleForTesting
2629
Future<void> migrateIsBlocked() async {
2730
_logger.info('Migrating isBlocked...');
2831
final pkgQuery = dbService.query<Package>()..filter('isBlocked =', true);
@@ -88,3 +91,18 @@ Future<void> migrateIsBlocked() async {
8891

8992
_logger.info('isBlocked migration completed.');
9093
}
94+
95+
Future<void> _removeKnownUnmappedFields() async {
96+
await for (final p in dbService.query<Package>().run()) {
97+
if (p.additionalProperties.isEmpty) continue;
98+
if (p.additionalProperties.containsKey('isWithheld') ||
99+
p.additionalProperties.containsKey('withheldReason')) {
100+
await withRetryTransaction(dbService, (tx) async {
101+
final pkg = await tx.lookupValue<Package>(p.key);
102+
pkg.additionalProperties.remove('isWithheld');
103+
pkg.additionalProperties.remove('withheldReason');
104+
tx.insert(pkg);
105+
});
106+
}
107+
}
108+
}

0 commit comments

Comments
 (0)