Skip to content

Commit b00dc78

Browse files
committed
recent-pms: Reimplement getRecentConversations using the nice new data.
This is only a little shorter in line count; but I think it has a good bit less complexity to take in. It's also doing quite a lot less work: instead of iterating through all the PMs (messages!) we know about, it iterates through just one item per PM *conversation*, in data that we've efficiently maintained as the messages came in. That's the big payoff of this server API feature. Or, relatedly: because this is so much more efficient, we're able to provide much more thorough data. The old way was fed primarily by `fetchPrivateMessages`, where we fetch the latest 100 PMs -- which for someone who uses PMs a lot could leave out conversations as recent as yesterday, and include just a handful of conversations. With current servers, the new way gets initialized from the user's last 1000 PMs. As a result, if you look at the PM-conversations screen with the app before this change and then after it, you'll probably see a lot more conversations. For me on czo, I see about 4x as many, extending far back into the past. Fixes: #3133
1 parent 8169025 commit b00dc78

File tree

2 files changed

+109
-11
lines changed

2 files changed

+109
-11
lines changed

src/pm-conversations/__tests__/pmConversationsSelectors-test.js

+56-6
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,70 @@
22
import Immutable from 'immutable';
33

44
import { getRecentConversations } from '../pmConversationsSelectors';
5+
import { keyOfExactUsers } from '../pmConversationsModel';
56
import { ALL_PRIVATE_NARROW_STR } from '../../utils/narrow';
67
import * as eg from '../../__tests__/lib/exampleData';
78
import { ZulipVersion } from '../../utils/zulipVersion';
89

10+
const keyForUsers = users =>
11+
users
12+
.map(u => u.user_id)
13+
.sort((a, b) => a - b)
14+
.map(String)
15+
.join(',');
16+
17+
describe('getRecentConversationsModern', () => {
18+
const accounts = [{ ...eg.selfAccount, zulipVersion: new ZulipVersion('2.1') }];
19+
const recentsKeyForUsers = users => keyOfExactUsers(users.map(u => u.user_id));
20+
21+
test('does its job', () => {
22+
const state = eg.reduxState({
23+
accounts,
24+
realm: eg.realmState({ user_id: eg.selfUser.user_id }),
25+
users: [eg.selfUser, eg.otherUser, eg.thirdUser],
26+
unread: {
27+
...eg.baseReduxState.unread,
28+
pms: [
29+
{ sender_id: eg.selfUser.user_id, unread_message_ids: [4] },
30+
{ sender_id: eg.otherUser.user_id, unread_message_ids: [1, 3] },
31+
],
32+
huddles: [
33+
{
34+
user_ids_string: keyForUsers([eg.selfUser, eg.otherUser, eg.thirdUser]),
35+
unread_message_ids: [2],
36+
},
37+
],
38+
},
39+
pmConversations: {
40+
// prettier-ignore
41+
map: Immutable.Map([
42+
[[], 4],
43+
[[eg.otherUser], 3],
44+
[[eg.otherUser, eg.thirdUser], 2],
45+
].map(([k, v]) => [recentsKeyForUsers(k), v])),
46+
sorted: Immutable.List(
47+
[[], [eg.otherUser], [eg.otherUser, eg.thirdUser]].map(recentsKeyForUsers),
48+
),
49+
},
50+
});
51+
52+
expect(getRecentConversations(state)).toEqual([
53+
{ key: eg.selfUser.user_id.toString(), keyRecipients: [eg.selfUser], msgId: 4, unread: 1 },
54+
{ key: eg.otherUser.user_id.toString(), keyRecipients: [eg.otherUser], msgId: 3, unread: 2 },
55+
{
56+
key: keyForUsers([eg.selfUser, eg.otherUser, eg.thirdUser]),
57+
keyRecipients: [eg.otherUser, eg.thirdUser].sort((a, b) => a.user_id - b.user_id),
58+
msgId: 2,
59+
unread: 1,
60+
},
61+
]);
62+
});
63+
});
64+
965
describe('getRecentConversationsLegacy', () => {
1066
const accounts = [{ ...eg.selfAccount, zulipVersion: new ZulipVersion('2.0') }];
1167
const userJohn = eg.makeUser();
1268
const userMark = eg.makeUser();
13-
const keyForUsers = users =>
14-
users
15-
.map(u => u.user_id)
16-
.sort((a, b) => a - b)
17-
.map(String)
18-
.join(',');
1969

2070
test('when no messages, return no conversations', () => {
2171
const state = eg.reduxState({

src/pm-conversations/pmConversationsSelectors.js

+53-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
/* @flow strict-local */
2+
import invariant from 'invariant';
23
import { createSelector } from 'reselect';
34

45
import type { GlobalState, Message, PmConversationData, Selector, User } from '../types';
56
import { getPrivateMessages } from '../message/messageSelectors';
6-
import { getAllUsersById, getOwnUser } from '../users/userSelectors';
7+
import { getAllUsersById, getOwnUser, getOwnUserId } from '../users/userSelectors';
78
import { getUnreadByPms, getUnreadByHuddles } from '../unread/unreadSelectors';
8-
import { pmUnreadsKeyFromMessage, pmKeyRecipientUsersFromMessage } from '../utils/recipient';
9+
import {
10+
pmUnreadsKeyFromMessage,
11+
pmKeyRecipientUsersFromMessage,
12+
pmKeyRecipientsFromIds,
13+
pmUnreadsKeyFromPmKeyIds,
14+
} from '../utils/recipient';
915
import { getServerVersion } from '../account/accountsSelectors';
1016
import * as model from './pmConversationsModel';
17+
import { type PmConversationsState } from './pmConversationsModel';
1118

1219
function unreadCount(unreadsKey, unreadPms, unreadHuddles): number {
1320
// This business of looking in one place and then the other is kind
@@ -65,9 +72,50 @@ export const getRecentConversationsLegacy: Selector<PmConversationData[]> = crea
6572
},
6673
);
6774

68-
export const getRecentConversationsModern: Selector<PmConversationData[]> = state =>
69-
// TODO implement for real
70-
getRecentConversationsLegacy(state);
75+
export const getRecentConversationsModern: Selector<PmConversationData[]> = createSelector(
76+
state => state.pmConversations,
77+
getUnreadByPms,
78+
getUnreadByHuddles,
79+
getAllUsersById,
80+
getOwnUserId,
81+
// This is defined separately, just below. When this is defined inline,
82+
// if there's a type error in it, the message Flow gives is often pretty
83+
// terrible: just highlighting the whole thing and pointing at something
84+
// in the `reselect` libdef. Defining it separately seems to help.
85+
getRecentConversationsModernImpl, // eslint-disable-line no-use-before-define
86+
);
87+
88+
function getRecentConversationsModernImpl(
89+
{ sorted, map }: PmConversationsState,
90+
unreadPms,
91+
unreadHuddles,
92+
allUsersById,
93+
ownUserId,
94+
): PmConversationData[] {
95+
return sorted
96+
.toSeq()
97+
.map(recentsKey => {
98+
const keyRecipients = pmKeyRecipientsFromIds(
99+
model.usersOfKey(recentsKey),
100+
allUsersById,
101+
ownUserId,
102+
);
103+
if (keyRecipients === null) {
104+
return null;
105+
}
106+
107+
const unreadsKey = pmUnreadsKeyFromPmKeyIds(keyRecipients.map(r => r.user_id), ownUserId);
108+
109+
const msgId = map.get(recentsKey);
110+
invariant(msgId !== undefined, 'pm-conversations: key in sorted should be in map');
111+
112+
const unread = unreadCount(unreadsKey, unreadPms, unreadHuddles);
113+
114+
return { key: unreadsKey, keyRecipients, msgId, unread };
115+
})
116+
.filter(Boolean)
117+
.toArray();
118+
}
71119

72120
const getServerIsOld: Selector<boolean> = createSelector(
73121
getServerVersion,

0 commit comments

Comments
 (0)