-
-
Notifications
You must be signed in to change notification settings - Fork 673
narrow: Stop storing emails! #4382
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1193a57
f8964ac
7a8739e
3bedb18
1c5557c
69b017b
a6798aa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,7 +18,6 @@ import ZulipAsyncStorage from './ZulipAsyncStorage'; | |
| import createMigration from '../redux-persist-migrate/index'; | ||
| import { provideLoggingContext } from './loggingContext'; | ||
| import { tryGetActiveAccount } from '../account/accountsSelectors'; | ||
| import { keyFromNarrow } from '../utils/narrow'; | ||
| import { objectFromEntries } from '../jsBackport'; | ||
|
|
||
| if (process.env.NODE_ENV === 'development') { | ||
|
|
@@ -226,29 +225,25 @@ const migrations: { [string]: (GlobalState) => GlobalState } = { | |
| // `AvatarURL`. | ||
| '18': dropCache, | ||
|
|
||
| // Change format of keys representing narrows, from JSON to our format. | ||
| '19': state => ({ | ||
| // Change format of keys representing narrows: from JSON to our format, | ||
| // then for PM narrows adding user IDs. | ||
| '21': state => ({ | ||
| ...dropCache(state), | ||
| drafts: objectFromEntries( | ||
| // Note this will migrate straight to our current format, even after | ||
| // that changes from when this migration was written! That saves us | ||
| // from duplicating `keyFromNarrow` here... but calls for care in | ||
| // migrations for future changes to `keyFromNarrow`. | ||
| Object.keys(state.drafts).map(key => [keyFromNarrow(JSON.parse(key)), state.drafts[key]]), | ||
| ), | ||
| // The old format was a rather hairy format that we don't want to | ||
| // permanently keep around the code to parse. For PMs, there's an | ||
| // extra wrinkle in that any conversion would require using additional | ||
| // information to look up the IDs. Drafts are inherently short-term, | ||
| // and are already discarded whenever switching between accounts; | ||
| // so we just drop them here. | ||
| drafts: {}, | ||
| }), | ||
|
|
||
| // Change format of keys representing PM narrows, adding user IDs. | ||
| '20': state => ({ | ||
| // Change format of keys representing PM narrows, dropping emails. | ||
| '22': state => ({ | ||
| ...dropCache(state), | ||
| drafts: objectFromEntries( | ||
| Object.keys(state.drafts) | ||
| // Just drop any old-style, email-only PM keys. Converting them | ||
| // would require using additional information to look up the IDs, | ||
| // which would make this more complex than any of our other | ||
| // migrations. Drafts are inherently short-term, and are already | ||
| // discarded whenever switching between accounts. | ||
| .filter(key => !key.startsWith('pm:s:')) | ||
| .map(key => key.replace(/^pm:d:(.*?):.*/s, 'pm:$1')) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good, I think In particular:
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for checking this!
(It'll have no PMs, that is -- it'll still have drafts for stream messages.)
Hmm. I think there's actually a bug here, though! In migration 19: Object.keys(state.drafts).map(key => [keyFromNarrow(JSON.parse(key)), state.drafts[key]]),the
The bug was introduced with #4346, where This probably means that my plan in #4339 (at #4339 (comment) ) wasn't the right one in retrospect. It would have been better to simply drop the drafts, or else bite the bullet and duplicate Happily no general users can have been affected at all, because we haven't made a release since this was introduced. I think I'll fix this by rolling up migrations 19 and 20 into a new migration 21, which just drops drafts entirely (plus
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh OK, great—I'm glad you found this! 🙂
SGTM. |
||
| .map(key => [key, state.drafts[key]]), | ||
| ), | ||
| }), | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,7 +32,7 @@ import { | |
| export opaque type Narrow = | ||
| | {| type: 'stream', streamName: string |} | ||
| | {| type: 'topic', streamName: string, topic: string |} | ||
| | {| type: 'pm', userIds: $ReadOnlyArray<number>, emails: $ReadOnlyArray<string> |} | ||
| | {| type: 'pm', userIds: $ReadOnlyArray<number> |} | ||
| | {| type: 'search', query: string |} | ||
| | {| type: 'all' | 'starred' | 'mentioned' | 'all-pm' |}; | ||
|
|
||
|
|
@@ -59,8 +59,8 @@ export const HOME_NARROW_STR: string = keyFromNarrow(HOME_NARROW); | |
| * accidentally disagreeing on whether to include the self-user, or on how | ||
| * to sort the list (by user ID vs. email), or neglecting to sort it at all. | ||
| */ | ||
| const pmNarrowInternal = (userIds: $ReadOnlyArray<number>, emails: string[]): Narrow => | ||
| Object.freeze({ type: 'pm', userIds, emails }); | ||
| const pmNarrowInternal = (userIds: $ReadOnlyArray<number>): Narrow => | ||
| Object.freeze({ type: 'pm', userIds }); | ||
|
|
||
| /** | ||
| * A PM narrow, either 1:1 or group. | ||
|
|
@@ -73,7 +73,7 @@ const pmNarrowInternal = (userIds: $ReadOnlyArray<number>, emails: string[]): Na | |
| * different form of input. | ||
| */ | ||
| export const pmNarrowFromRecipients = (recipients: PmKeyRecipients): Narrow => | ||
| pmNarrowInternal(recipients.map(r => r.id), recipients.map(r => r.email)); | ||
| pmNarrowInternal(recipients); | ||
|
|
||
| /** | ||
| * A PM narrow, either 1:1 or group. | ||
|
|
@@ -85,7 +85,7 @@ export const pmNarrowFromRecipients = (recipients: PmKeyRecipients): Narrow => | |
| * single specific user. | ||
| */ | ||
| export const pmNarrowFromUsers = (recipients: PmKeyUsers): Narrow => | ||
| pmNarrowInternal(recipients.map(r => r.user_id), recipients.map(r => r.email)); | ||
| pmNarrowInternal(recipients.map(r => r.user_id)); | ||
|
|
||
| /** | ||
| * FOR TESTS ONLY. Like pmNarrowFromUsers, but without validation. | ||
|
|
@@ -105,7 +105,7 @@ export const pmNarrowFromUsers = (recipients: PmKeyUsers): Narrow => | |
| // annoying thing is just that that requires an ownUserId value. | ||
| export const pmNarrowFromUsersUnsafe = (recipients: UserOrBot[]): Narrow => { | ||
| recipients.sort((a, b) => a.user_id - b.user_id); | ||
| return pmNarrowInternal(recipients.map(r => r.user_id), recipients.map(r => r.email)); | ||
| return pmNarrowInternal(recipients.map(r => r.user_id)); | ||
| }; | ||
|
|
||
| /** | ||
|
|
@@ -115,8 +115,7 @@ export const pmNarrowFromUsersUnsafe = (recipients: UserOrBot[]): Narrow => { | |
| * statically has just one other user it's a bit more convenient because it | ||
| * doesn't require going through our `recipient` helpers. | ||
| */ | ||
| export const pm1to1NarrowFromUser = (user: UserOrBot): Narrow => | ||
| pmNarrowInternal([user.user_id], [user.email]); | ||
| export const pm1to1NarrowFromUser = (user: UserOrBot): Narrow => pmNarrowInternal([user.user_id]); | ||
|
|
||
| export const specialNarrow = (operand: string): Narrow => { | ||
| if (operand === 'starred') { | ||
|
|
@@ -153,7 +152,7 @@ export const SEARCH_NARROW = (query: string): Narrow => Object.freeze({ type: 's | |
|
|
||
| type NarrowCases<T> = {| | ||
| home: () => T, | ||
| pm: (emails: $ReadOnlyArray<string>, ids: $ReadOnlyArray<number>) => T, | ||
| pm: (ids: $ReadOnlyArray<number>) => T, | ||
| starred: () => T, | ||
| mentioned: () => T, | ||
| allPrivate: () => T, | ||
|
|
@@ -171,7 +170,7 @@ export function caseNarrow<T>(narrow: Narrow, cases: NarrowCases<T>): T { | |
| switch (narrow.type) { | ||
| case 'stream': return cases.stream(narrow.streamName); | ||
| case 'topic': return cases.topic(narrow.streamName, narrow.topic); | ||
| case 'pm': return cases.pm(narrow.emails, narrow.userIds); | ||
| case 'pm': return cases.pm(narrow.userIds); | ||
| case 'search': return cases.search(narrow.query); | ||
| case 'all': return cases.home(); | ||
| case 'starred': return cases.starred(); | ||
|
|
@@ -237,8 +236,7 @@ export function caseNarrowDefault<T>( | |
| * something like `base64Utf8Encode` to encode into the permitted subset. | ||
| */ | ||
| // The arbitrary Unicode codepoints come from (a) stream names and topics, | ||
| // and (b) our use of `\x00` as a delimiter. Also perhaps email addresses, | ||
| // if it's possible for those to have exciting characters in them. | ||
| // and (b) our use of `\x00` as a delimiter. | ||
| export function keyFromNarrow(narrow: Narrow): string { | ||
| // The ":s" (for "string") in several of these is to keep them disjoint, | ||
| // out of an abundance of caution, from future keys that use numeric IDs. | ||
|
|
@@ -254,7 +252,8 @@ export function keyFromNarrow(narrow: Narrow): string { | |
| topic: (streamName, topic) => `topic:s:${streamName}\x00${topic}`, | ||
|
|
||
| // An earlier version had `pm:s:`. | ||
| pm: (emails, ids) => `pm:d:${ids.join(',')}:${emails.join(',')}`, | ||
| // Another had `pm:d:`. | ||
| pm: ids => `pm:${ids.join(',')}`, | ||
|
|
||
| home: () => 'all', | ||
| starred: () => 'starred', | ||
|
|
@@ -295,17 +294,8 @@ export const parseNarrow = (narrowStr: string): Narrow => { | |
| } | ||
|
|
||
| case 'pm:': { | ||
| // The `/s` regexp flag means the `.` patterns match absolutely | ||
| // anything. By default they reject certain "newline" characters, | ||
| // which might be impossible in email addresses but it's simplest | ||
| // not to have to care. | ||
| const match = /^d:(.*?):(.*)/s.exec(rest); | ||
| if (!match) { | ||
| throw makeError(); | ||
| } | ||
| const ids = match[1].split(',').map(s => Number.parseInt(s, 10)); | ||
| const emails = match[2].split(','); | ||
| return pmNarrowInternal(ids, emails); | ||
| const ids = rest.split(',').map(s => Number.parseInt(s, 10)); | ||
| return pmNarrowInternal(ids); | ||
| } | ||
|
|
||
| case 'search:': { | ||
|
|
@@ -336,10 +326,10 @@ export const isHomeNarrow = (narrow?: Narrow): boolean => | |
| !!narrow && caseNarrowDefault(narrow, { home: () => true }, () => false); | ||
|
|
||
| export const is1to1PmNarrow = (narrow?: Narrow): boolean => | ||
| !!narrow && caseNarrowDefault(narrow, { pm: (emails, ids) => ids.length === 1 }, () => false); | ||
| !!narrow && caseNarrowDefault(narrow, { pm: ids => ids.length === 1 }, () => false); | ||
|
|
||
| export const isGroupPmNarrow = (narrow?: Narrow): boolean => | ||
| !!narrow && caseNarrowDefault(narrow, { pm: (emails, ids) => ids.length > 1 }, () => false); | ||
| !!narrow && caseNarrowDefault(narrow, { pm: ids => ids.length > 1 }, () => false); | ||
|
|
||
| /** | ||
| * The "PM key recipients" IDs for a PM narrow; else error. | ||
|
|
@@ -348,7 +338,7 @@ export const isGroupPmNarrow = (narrow?: Narrow): boolean => | |
| * `PmKeyUsers`, but contains only their user IDs. | ||
| */ | ||
| export const userIdsOfPmNarrow = (narrow: Narrow): $ReadOnlyArray<number> => | ||
| caseNarrowPartial(narrow, { pm: (emails, ids) => ids }); | ||
| caseNarrowPartial(narrow, { pm: ids => ids }); | ||
|
|
||
| /** | ||
| * The stream name for a stream or topic narrow; else error. | ||
|
|
@@ -399,14 +389,28 @@ export const isSearchNarrow = (narrow?: Narrow): boolean => | |
| /** | ||
| * Convert the narrow into the form used in the Zulip API at get-messages. | ||
| */ | ||
| export const apiNarrowOfNarrow = (narrow: Narrow): ApiNarrow => | ||
| export const apiNarrowOfNarrow = ( | ||
| narrow: Narrow, | ||
| allUsersById: Map<number, UserOrBot>, | ||
| ): ApiNarrow => | ||
| caseNarrow(narrow, { | ||
| stream: streamName => [{ operator: 'stream', operand: streamName }], | ||
| topic: (streamName, topic) => [ | ||
| { operator: 'stream', operand: streamName }, | ||
| { operator: 'topic', operand: topic }, | ||
| ], | ||
| pm: emails => [{ operator: 'pm-with', operand: emails.join(',') }], | ||
| pm: ids => { | ||
| const emails = []; | ||
| for (const id of ids) { | ||
| const email = allUsersById.get(id)?.email; | ||
| if (email === undefined) { | ||
| throw new Error('apiNarrowOfNarrow: missing user'); | ||
| } | ||
| emails.push(email); | ||
| } | ||
| // TODO(server-2.1): just send IDs instead | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If it makes a big difference to the amount of work the server has to do (and if that's important to minimize), we could check the stored server version in Redux and courteously send IDs to the newer servers, while still sending emails to older ones. But it might not make much of a difference, and we may be dropping support for those older server versions soon-ish anyway. 🙂
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I don't think it materially affects the work the server has to do. It'll want to look up the users in the database in any event (for one thing, to make sure they're valid users that are in your realm); and the database will have indexes so that looking up by ID or by email are both fast, because those are both common operations. |
||
| return [{ operator: 'pm-with', operand: emails.join(',') }]; | ||
| }, | ||
| search: query => [{ operator: 'search', operand: query }], | ||
| home: () => [], | ||
| starred: () => [{ operator: 'is', operand: 'starred' }], | ||
|
|
@@ -441,7 +445,7 @@ export const isMessageInNarrow = ( | |
| message.type === 'stream' | ||
| && streamName === streamNameOfStreamMessage(message) | ||
| && topic === message.subject, | ||
| pm: (emails, ids) => { | ||
| pm: ids => { | ||
| if (message.type !== 'private') { | ||
| return false; | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like
keyFromNarrowandobjectFromEntriesneed their imports removed (but then don't forget to bring backobjectFromEntries's for migration 22 🙂).