Skip to content

narrow: Switch to a reasonable representation, and store user IDs#4346

Merged
gnprice merged 8 commits intozulip:masterfrom
gnprice:pr-narrow-store-userids
Dec 23, 2020
Merged

narrow: Switch to a reasonable representation, and store user IDs#4346
gnprice merged 8 commits intozulip:masterfrom
gnprice:pr-narrow-store-userids

Conversation

@gnprice
Copy link
Copy Markdown
Member

@gnprice gnprice commented Dec 18, 2020

This follows on #4342 and its predecessors, and accomplishes part of #4333: at the end of this PR, we now have user IDs in our internal representation of PM narrows.

After this, in the next PR or two we'll go on to switch all the various places we consume narrows to use the IDs instead of the emails -- which as mentioned at #4035 (comment) will include completing #4035 -- and stop storing emails in our narrows at all.

Thanks to all the prep work in the preceding several PRs, not much actually has to change in this one -- we've already quite thoroughly encapsulated the details of the Narrow type, and also eliminated nearly all the places where we were constructing PM narrows without passing a user ID to the constructor along with an email. Most of what's in this PR is just taking care of the last few of those.

@gnprice gnprice requested a review from chrisbobbe December 18, 2020 09:02
Copy link
Copy Markdown
Contributor

@chrisbobbe chrisbobbe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @gnprice! This LGTM pending a few small comments, below.

Comment thread src/utils/narrow.js Outdated
];
export const specialNarrow = (operand: string): Narrow => {
if (operand === 'starred') {
return { type: 'starred' };
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eb965a7 (narrow [nfc]: Switch the Narrow type to a reasonable structure!)

Should we Object.freeze the returned object, like we do for other Narrows in this commit?

Comment thread src/utils/narrow.js Outdated
if (operand === 'private') {
return { type: 'all-pm' };
}
throw new Error(`specialNarrow: got invalid operand: ${operand}`);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+  throw new Error(`specialNarrow: got invalid operand: ${operand}`);

Would "unsupported" be slightly better than "invalid"? I'm thinking of valid but unsupported narrows like is:unread, which could reasonably be encoded in links that specialNarrow makes narrows from.

Comment thread src/utils/narrow.js Outdated

case 'pm:': {
if (!rest.startsWith('s:')) {
const match = /^d:(.*?):(.*)/.exec(rest);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah OK, I think I've worked out what we're doing here:

The ? in the first capture group, (.*?), makes the * quantifier lazy instead of greedy, which it would be by default. This is fine because the IDs are numeric and obviously don't contain a :. It's also necessary, because email addresses might contain colons (er, I think? Wikipedia gives the example "jsmith@[IPv6:2001:db8::1]", which I think may be permitted by IETF RFC 2821 section 4.1.3).

It'll be simpler to think about once we've taken emails out of the picture. If that weren't planned, I'd say we might want to have an explanatory code comment here.

This reminds me of what we're doing for topic narrows, starting in af1713a (though it's seeming like that'll need to be reworked because it caused a regression; the \x00 character we use there (which has a very similar role to the : here) doesn't round-trip into being stored as the value of the data-narrow attribute in our message-list HTML; see discussion).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ? in the first capture group, (.*?), makes the * quantifier lazy instead of greedy, which it would be by default.

Yep!

It's also necessary, because email addresses might contain colons (er, I think? Wikipedia gives the example "jsmith@[IPv6:2001:db8::1]", which I think may be permitted by IETF RFC 2821 section 4.1.3).

Yeah, good find with that example. I didn't even think about whether an email address could contain a colon. But the great thing about using .*? here is that we don't have to, because it will match up to that first colon (which has to be the one we put there as a delimiter), and then everything (*) after that is the email addresses.

In general for capturing contents up to a delimiter, .*? is what you want rather than .*, for just this reason. It similarly appears in the other regexps in this parseNarrow function.

I did deliberately put the IDs first and the emails at the end, so that the emails would be the part in that position where we don't have to think about what's there.

(*) Though looking again at the topic: case reminds me that there is an asterisk on that, because without the /s modifier there are a few characters the . pattern rejects. I'm not sure if those characters are possible in email addresses -- I'm sure ASCII newline isn't, but there are a couple of other "newline" characters that wrinkle applies to and I'm not certain if they can appear. I'll add /s there too so we don't have to think about it.

(though it's seeming like that'll need to be reworked because it caused a regression; the \x00 character we use there (which has a very similar role to the : here) doesn't round-trip into being stored as the value of the data-narrow attribute in our message-list HTML; see discussion).

(We ended up fixing that issue at a different layer, in #4352.)

This has no callers at all except its own tests.  The only one it's
ever had was a trivial one inside the `narrow` module -- and that
appeared nearly a year after the function itself was introduced.
Because of all the work we've done over the last few series to
encapsulate the details of the Narrow type within this module,
not much has to change!  Just the constructors; the pattern-match
engine caseNarrow; and apiNarrowFromNarrow, where we'd been taking
a shortcut using the fact that Narrow secretly was ApiNarrow.

Well, those and one call site to `specialNarrow`, where we newly
enforce an invariant we'd long assumed about what kind of `is:foo`
narrows we support.
This isn't as good as not even *using* emails, in favor of
user IDs... but it's a step on the way there, as well as a
small nice simplification in itself.
This sets up a mass conversion we'll do in the next commit.
Done with the following commands:

  $ perl -i -0pe '
        s#pmNarrowFromEmail\((.*?)\.email\)#pm1to1NarrowFromUser($1)#g
          && s/pmNarrowFromEmail\b/pm1to1NarrowFromUser/g
      ' src/**/__tests__/*.js
  $ tools/fmt

then fixing one duplicate import, in narrow-test.js.
We already do this for opening a notification for a group PM; now do
the same for 1:1 PMs too.  If we manage to have a notification where
we don't know of a sender with that email -- which can happen if
e.g. the sender has changed it since the notification was originally
sent -- then this will cause us not to try to navigate to the
conversation at all, which is an improvement over anything the
previous behavior might have been: navigating to a conversation
where we show something broken-looking, or crash.

The motivation for this is so that we pass the Narrow constructor a
user ID as well as an email, so that we can start storing user IDs in
Narrow objects and start using them in all the code that consumes them.
This is the last spot in our codebase where we're not doing so.

Really we should use the sender ID from the server.  That started in
Zulip Server 1.8.0, according to the handy comments in FcmMessage.kt.
We leave that be for now, though -- this email use is easy to find
with a grep for `email`.
After all that prep work... this just works.  Apart from a storage
migration, only this module has to change, only in a few places --
and Flow pointed them all out.
@gnprice gnprice force-pushed the pr-narrow-store-userids branch from aa6fd7e to df64214 Compare December 23, 2020 05:29
@gnprice gnprice merged commit df64214 into zulip:master Dec 23, 2020
@gnprice
Copy link
Copy Markdown
Member Author

gnprice commented Dec 23, 2020

Thanks for the review! Merged after making the suggested changes.

@gnprice gnprice deleted the pr-narrow-store-userids branch December 23, 2020 05:33
gnprice added a commit to gnprice/zulip-mobile that referenced this pull request Jan 6, 2021
As the comment in migration 19 said, we were doing something a bit
risky there in order to avoid duplicating the then-current version
of `keyFromNarrow`.  We simply invoked `keyFromNarrow` directly --
which called for being careful about migrations for any future change
we made to `keyFromNarrow`.

Then a bit later we changed `keyFromNarrow` quite dramatically, and
didn't pay attention to migrations at all.  Oops.

Specifically, in cf4207f we changed the `Narrow` type to have quite
a different structure.  That's the type that `keyFromNarrow` accepts
-- so the result is that `keyFromNarrow` stopped working on the old
values.  Which are exactly what `JSON.parse` is going to supply us
when this migration is run.  The result is probably that if the user
has any drafts when they take an upgrade that spans migration 19
and commit cf4207f, the app will crash on startup.

We'd probably have found this bug at the alpha stage when next making
a release, so it wouldn't have made it to users other than ourselves.
Better to fix ASAP, though.

The plan for migration 19, as described here:
  zulip#4339 (comment)
knowingly involved breaking the basic rule of migrations: don't invoke
any of the app's model code, because it will change.  I figured it was
OK because there were changes coming up soon which I'd planned out how
I'd add migrations for.  Then later the same week I sent zulip#4346, which
followed that plan... and I hadn't thought through this particular
interaction, and it introduced this bug.  So this is a fresh lesson in
the basic rule of migrations: don't invoke any of the app's model code,
because it will change.

See discussion where we found this bug:
  zulip#4382 (comment)
gnprice added a commit to gnprice/zulip-mobile that referenced this pull request Jan 6, 2021
This completes a major objective of the long string of refactoring
that appeared in the series of PRs zulip#4330, zulip#4332, zulip#4335, zulip#4339, zulip#4342,
then zulip#4346, zulip#4356, zulip#4361, zulip#4364, and most recently zulip#4368.  After this
change, the portion of zulip#4333 that's about PMs, emails, and user IDs --
aka the portion of zulip#3764 that's about narrows -- is complete.

Still open from zulip#4333 is to convert stream and topic narrows from
stream names to stream IDs.  I'm hopeful that that will be simpler:
(a) unlike the situation for PMs, there's just one stream mentioned
at a time, so there's no question of sorting, and (b) there isn't
the "include self or not" complication that's bedeviled much of our
code related to PMs.  In other words, stream and topic narrows
don't suffer from zulip#4035.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants