Skip to content

Commit

Permalink
Better admin action for listing moderation cases. (#7996)
Browse files Browse the repository at this point in the history
  • Loading branch information
isoos committed Aug 29, 2024
1 parent 52f37fd commit 1eebaaf
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 10 deletions.
62 changes: 55 additions & 7 deletions app/lib/admin/actions/moderation_case_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,39 @@
// 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/search/search_form.dart';
import 'package:clock/clock.dart';
import 'package:pub_dev/admin/models.dart';
import 'package:pub_dev/shared/datastore.dart';

import 'actions.dart';

final moderationCaseList = AdminAction(
name: 'moderation-case-list',
summary: 'List the currently active (or resolved) ModerationCase entities.',
summary: 'List ModerationCase entities.',
description: '''
List the ModerationCase entities with filter options.
List ModerationCase entities with filter options.
''',
options: {
'sort': 'Sort by the given attribute: `opened` (default), `resolved`.',
'status': '`pending` | `resolved` | '
'${ModerationStatus.resolveValues.map((v) => '`$v`').join(' | ')}',
'kind': '`appeal` | `notification`',
'subject': 'The (substring of) the subject on the moderation case.',
'density': '`caseIds` (default) | `compact` | `expanded`',
'past':
'Limit the results opened (or resolved depending on `sort`) using "2w" or other time ranges.'
},
invoke: (options) async {
final sort = options['sort'] ?? 'opened';
final query = dbService.query<ModerationCase>()..limit(20);
final status = options['status'];
final kind = options['kind'];
final subject = options['subject'];
final density = options['density'] ?? 'caseIds';
final past = options['past'];
final pastDuration = past == null ? null : parseTime(past);

final query = dbService.query<ModerationCase>();
switch (sort) {
case 'opened':
query.order('-opened');
Expand All @@ -31,9 +46,42 @@ List the ModerationCase entities with filter options.
throw InvalidInputException('invalid sort value');
}

final list = await query.run().toList();
return {
'cases': list.map((e) => e.toDebugInfo()).toList(),
};
final list = await query.run().where((mc) {
if (status == 'pending' && mc.status != ModerationStatus.pending) {
return false;
}
if (status == 'resolved' && mc.status == ModerationStatus.pending) {
return false;
}
if (kind != null && mc.kind != kind) {
return false;
}
if (subject != null && !mc.subject.contains(subject)) {
return false;
}
final now = clock.now();
if (pastDuration != null) {
if (sort == 'opened' && now.difference(mc.opened) > pastDuration) {
return false;
}
if (sort == 'resolved' &&
(mc.resolved == null ||
now.difference(mc.resolved!) > pastDuration)) {
return false;
}
}
return true;
}).toList();

switch (density) {
case 'caseIds':
return {'caseIds': list.map((e) => e.caseId).toList()};
case 'compact':
return {'cases': list.map((e) => e.toCompactInfo()).toList()};
case 'expanded':
return {'cases': list.map((e) => e.toDebugInfo()).toList()};
default:
throw InvalidInputException('invalid density value');
}
},
);
20 changes: 18 additions & 2 deletions app/lib/admin/models.dart
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,18 @@ class ModerationCase extends db.ExpandoModel<String> {
setActionLog(log);
}

Map<String, dynamic> toCompactInfo() {
return {
'caseId': caseId,
'reporterEmail': reporterEmail,
'kind': kind,
'opened': opened.toIso8601String(),
'resolved': resolved?.toIso8601String(),
'subject': subject,
if (appealedCaseId != null) 'appealedCaseId': appealedCaseId,
};
}

Map<String, dynamic> toDebugInfo() {
return {
'caseId': caseId,
Expand Down Expand Up @@ -222,15 +234,19 @@ abstract class ModerationStatus {
static const moderationUpheld = 'moderation-upheld';
static const moderationReverted = 'moderation-reverted';

static const _values = [
pending,
static const resolveValues = [
noAction,
moderationApplied,
noActionUpheld,
noActionReverted,
moderationUpheld,
moderationReverted,
];
static const _values = [
pending,
...resolveValues,
];

static bool isValidStatus(String value) => _values.contains(value);
static bool wasModerationApplied(String value) =>
value == ModerationStatus.moderationApplied ||
Expand Down
4 changes: 3 additions & 1 deletion app/test/admin/moderation_case_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,9 @@ void main() {

final list = await api.adminInvokeAction(
'moderation-case-list',
AdminInvokeActionArguments(arguments: {}),
AdminInvokeActionArguments(arguments: {
'density': 'expanded',
}),
);
expect(list.output, {
'cases': [
Expand Down

0 comments on commit 1eebaaf

Please sign in to comment.