Skip to content

Commit c4744fb

Browse files
committed
autocomplete: Add "human vs. bot users" criterion
In @-mention autocomplete, users are suggested based on: 1. Recent activity in the current topic/stream. 2. Recent DM conversations. 3. Human vs. Bot users (human users come first).
1 parent 5c70c76 commit c4744fb

File tree

2 files changed

+80
-10
lines changed

2 files changed

+80
-10
lines changed

lib/model/autocomplete.dart

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -235,13 +235,16 @@ class MentionAutocompleteView extends ChangeNotifier {
235235
required PerAccountStore store,
236236
}) {
237237
if (streamId != null) {
238-
final result = compareByRecency(userA, userB,
238+
final recencyResult = compareByRecency(userA, userB,
239239
streamId: streamId,
240240
topic: topic,
241241
store: store);
242-
if (result != 0) return result;
242+
if (recencyResult != 0) return recencyResult;
243243
}
244-
return compareByDms(userA, userB, store: store);
244+
final dmsResult = compareByDms(userA, userB, store: store);
245+
if (dmsResult != 0) return dmsResult;
246+
247+
return compareByBotStatus(userA, userB);
245248
}
246249

247250
/// Determines which of the two users has more recent activity (messages sent
@@ -309,6 +312,20 @@ class MentionAutocompleteView extends ChangeNotifier {
309312
};
310313
}
311314

315+
/// Compares the bot status of two users and returns an integer indicating their order.
316+
///
317+
/// Returns `-1` if `userA` is human and `userB` is bot, returns `1` if `userA`
318+
/// is bot and `userB` is human, and returns `0` if both users have the same
319+
/// bot status.
320+
@visibleForTesting
321+
static int compareByBotStatus(User userA, User userB) {
322+
return switch ((userA.isBot, userB.isBot)) {
323+
(false, true) => -1,
324+
(true, false) => 1,
325+
_ => 0,
326+
};
327+
}
328+
312329
@override
313330
void dispose() {
314331
store.autocompleteViewManager.unregisterMentionAutocomplete(this);

test/model/autocomplete_test.dart

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,26 @@ void main() {
490490
});
491491
});
492492

493+
group('compareByBotStatus', () {
494+
final humanUser = eg.user(isBot: false);
495+
final botUser = eg.user(isBot: true);
496+
497+
int compareAB(User a, User b) => MentionAutocompleteView.compareByBotStatus(a, b);
498+
499+
test('userA is human, userB is bot -> favor userA', () {
500+
check(compareAB(humanUser, botUser)).isLessThan(0);
501+
});
502+
503+
test('userA is bot, userB is human -> favor userB', () {
504+
check(compareAB(botUser, humanUser)).isGreaterThan(0);
505+
});
506+
507+
test('both users have the same bot status -> favor none', () {
508+
check(compareAB(humanUser, humanUser)).equals(0);
509+
check(compareAB(botUser, botUser)).equals(0);
510+
});
511+
});
512+
493513
group('ranking across signals', () {
494514
void checkPrecedes(Narrow narrow, User userA, Iterable<User> usersB) {
495515
final view = MentionAutocompleteView.init(store: store, narrow: narrow);
@@ -509,8 +529,17 @@ void main() {
509529
}
510530
}
511531

512-
test('TopicNarrow: topic recency > stream recency > DM recency', () async {
513-
final users = List.generate(5, (i) => eg.user());
532+
test('TopicNarrow: topic recency > stream recency > DM recency '
533+
'> human vs. bot user', () async {
534+
final users = [
535+
eg.user(),
536+
eg.user(),
537+
eg.user(isBot: true),
538+
eg.user(),
539+
eg.user(),
540+
eg.user(),
541+
eg.user(isBot: true),
542+
];
514543
final stream = eg.stream();
515544
final narrow = TopicNarrow(stream.streamId, 'this');
516545
await prepare(users: users, messages: [
@@ -525,25 +554,43 @@ void main() {
525554
checkPrecedes(narrow, users[1], users.skip(2));
526555
checkPrecedes(narrow, users[2], users.skip(3));
527556
checkRankEqual(narrow, [users[3], users[4]]);
557+
checkPrecedes(narrow, users[5], users.skip(6));
528558
});
529559

530-
test('ChannelNarrow: stream recency > DM recency', () async {
531-
final users = List.generate(4, (i) => eg.user());
560+
test('ChannelNarrow: stream recency > DM recency > human vs. bot user', () async {
561+
final users = [
562+
eg.user(isBot: true),
563+
eg.user(),
564+
eg.user(),
565+
eg.user(),
566+
eg.user(),
567+
eg.user(isBot: true),
568+
];
532569
final stream = eg.stream();
533570
final narrow = ChannelNarrow(stream.streamId);
534571
await prepare(users: users, messages: [
535572
eg.streamMessage(sender: users[1], stream: stream),
536573
eg.streamMessage(sender: users[0], stream: stream),
537574
eg.dmMessage(from: users[2], to: [users[3], eg.selfUser]),
538575
eg.dmMessage(from: users[1], to: [eg.selfUser]),
576+
eg.dmMessage(from: users[4], to: [users[5], eg.selfUser]),
539577
]);
540578
checkPrecedes(narrow, users[0], users.skip(1));
541579
checkPrecedes(narrow, users[1], users.skip(2));
542580
checkRankEqual(narrow, [users[2], users[3]]);
581+
checkPrecedes(narrow, users[4], users.skip(5));
543582
});
544583

545-
test('DmNarrow: DM recency > this-conversation recency or stream recency', () async {
546-
final users = List.generate(4, (i) => eg.user());
584+
test('DmNarrow: DM recency > this-conversation recency or stream recency '
585+
'or human vs. bot user', () async {
586+
final users = [
587+
eg.user(isBot: true),
588+
eg.user(),
589+
eg.user(),
590+
eg.user(),
591+
eg.user(),
592+
eg.user(isBot: true),
593+
];
547594
await prepare(users: users, messages: [
548595
eg.dmMessage(from: users[3], to: [eg.selfUser]),
549596
eg.dmMessage(from: users[1], to: [users[2], eg.selfUser]),
@@ -562,6 +609,7 @@ void main() {
562609
checkRankEqual(narrow, [users[1], users[2]]);
563610
checkPrecedes(narrow, users[1], users.skip(3));
564611
checkPrecedes(narrow, users[2], users.skip(3));
612+
checkPrecedes(narrow, users[4], users.skip(5));
565613
}
566614
});
567615

@@ -598,6 +646,8 @@ void main() {
598646
eg.user(userId: 3, fullName: 'User Three'),
599647
eg.user(userId: 4, fullName: 'User Four'),
600648
eg.user(userId: 5, fullName: 'User Five'),
649+
eg.user(userId: 6, fullName: 'User Six', isBot: true),
650+
eg.user(userId: 7, fullName: 'User Seven'),
601651
];
602652

603653
await prepare(users: users, messages: [
@@ -613,14 +663,17 @@ void main() {
613663
// The order should be:
614664
// 1. Users most recent in the current topic/stream.
615665
// 2. Users most recent in the DM conversations.
666+
// 3. Human vs. Bot users (human users come first).
616667
check(await getResults(topicNarrow, MentionAutocompleteQuery('')))
617-
.deepEquals([1, 5, 4, 2, 3]);
668+
.deepEquals([1, 5, 4, 2, 3, 7, 6]);
618669

619670
// Check the ranking applies also to results filtered by a query.
620671
check(await getResults(topicNarrow, MentionAutocompleteQuery('t')))
621672
.deepEquals([2, 3]);
622673
check(await getResults(topicNarrow, MentionAutocompleteQuery('f')))
623674
.deepEquals([5, 4]);
675+
check(await getResults(topicNarrow, MentionAutocompleteQuery('s')))
676+
.deepEquals([7, 6]);
624677
});
625678
});
626679
}

0 commit comments

Comments
 (0)