Skip to content

Migrate navigateToAccountDetails and getUserIsActive to user IDs #3968

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

Merged
merged 10 commits into from
Mar 26, 2020

Conversation

agrawal-d
Copy link
Member

@agrawal-d agrawal-d commented Mar 12, 2020

This is a rough initial PR. Feedback is appreciated. This was done to make pull #3878 mergeable, as per #3878 (comment).

This PR will partially address #3764.

@agrawal-d agrawal-d force-pushed the userid branch 2 times, most recently from b99f4cf to b4bdc0d Compare March 12, 2020 14:26
@agrawal-d
Copy link
Member Author

@ray-kraesig @gnprice, please review.

Copy link
Contributor

@rk-for-zulip rk-for-zulip left a comment

Choose a reason for hiding this comment

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

A few things, but looks good.

(The second commit's message and summary use JavaScript-style "Id" rather than English-style "ID" in a couple of places.)

Comment on lines 37 to 41
const user = getAllUsersByEmail(state).get(props.narrow[0].operand);
if (user === undefined) {
return -1;
}
return user.user_id;
Copy link
Contributor

Choose a reason for hiding this comment

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

This could be shortened to getAllUsersByEmail(state).get(props.narrow[0].operand)?.user_id ?? -1.

Preferably, though, this should be number | void rather than using a sentinel value. (Perhaps AccountDetailsScreen's prop should even be number | void? We've been quietly passing bad email-strings to it on failure for so long... 😞)

Copy link
Member Author

@agrawal-d agrawal-d Mar 19, 2020

Choose a reason for hiding this comment

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

I've used this in the latest push, but do not understand the syntax. Can you please explain?
?? looks like the nullish coalescing operator, and ? looks like the ternary operator.
But then,

  • Ternary operator requires three operands. And here only 2 have been passed.
  • What is the object that ?? is checking for null ?
  • I also do not understand ?. - what does it do?
  • Why is this getAllUsersByEmail(state).get(props.narrow[0].operand)?.user_id :undefined) incorrect syntax?

Copy link
Contributor

@rk-for-zulip rk-for-zulip Mar 19, 2020

Choose a reason for hiding this comment

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

?. is not part of the ternary operator; it's the optional chaining operator.

(EDIT: It's a bit difficult to Google, but surprisingly, not impossible.)

Copy link
Member Author

@agrawal-d agrawal-d Mar 20, 2020

Choose a reason for hiding this comment

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

Thanks. I had tried '?.' in quotes, but didn't get any (immediately) useful results.

@@ -200,7 +200,7 @@ export const handleMessageListEvent = (props: Props, _: GetText, event: MessageL
break;

case 'avatar':
props.dispatch(navigateToAccountDetails(event.fromEmail));
props.dispatch(navigateToAccountDetails(parseInt(event.fromUserId, 10)));
Copy link
Contributor

Choose a reason for hiding this comment

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

This leaks NaN. (And should probably log an error on parse-failure, including event.fromUserId as data in extras.)

@agrawal-d
Copy link
Member Author

@ray-kraesig I've pushed some changes. Please re-review.

componentDidMount() {
const { user } = this.props;
if (user === undefined) {
logging.error('User object passed is undefined.');
Copy link
Contributor

Choose a reason for hiding this comment

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

Unfortunately, at some point in the last few releases we lost the ability to reconstruct the source file/line information from these calls. 😞 This will need to identify the error site: e.g., 'AccountDetailsScreen created with invalid user', or something similar.

Copy link
Member

Choose a reason for hiding this comment

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

Unfortunately, at some point in the last few releases we lost the ability to reconstruct the source file/line information from these calls. 😞

Hmm! It looks like we don't have an issue for this -- @ray-kraesig , would you file one with whatever information you have? It'd be good to fix, and that will help us at least track what we know about it.

Copy link
Member

Choose a reason for hiding this comment

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

Ah, perhaps you're thinking of #3809? Just happened across that thread by chance.

Comment on lines 204 to 206
try {
userId = parseInt(event.fromUserId, 10);
} catch (e) {
Copy link
Contributor

Choose a reason for hiding this comment

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

NaNs don't raise exceptions. A try-catch will have no effect.

(See parseInt and NaN for more information.)

Comment on lines 98 to 112
user: (() => {
const { userId } = props.navigation.state.params;
return userId === undefined ? userId : getAllUsersById(state).get(userId);
})(),
isActive: (() => {
const { userId } = props.navigation.state.params;
if (userId === undefined) {
return false;
}
const user = getAllUsersById(state).get(userId);
if (!user) {
return false;
}
return getUserIsActive(state, user.email);
})(),
Copy link
Contributor

Choose a reason for hiding this comment

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

For future reference – isActive effectively needs the value of user, so you might as well make that explicit (and also avoid recomputing it). For example:

... connect((state, props) => {
  const { userId } = props.navigation.state.params;
  const user: number | void = ...
  const isActive: boolean = ...
  return { user, isActive };
}))(...

(I say "for future reference" since I'm aware most of this goes away in the next commit. 🙃 No need to take action here.)

Comment on lines 102 to 105
isActive:
props.navigation.state.params.userId === undefined
? false
: getUserIsActive(state, props.navigation.state.params.userId),
Copy link
Contributor

Choose a reason for hiding this comment

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

This is essentially the same computational structure as for user, above; the two should probably be structured similarly. (And, if it's simple enough to do so, partially coalesced.)

Copy link
Member

@gnprice gnprice left a comment

Choose a reason for hiding this comment

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

Thanks @agrawal-d, and thanks @ray-kraesig for the reviews!

Comments below.

Comment on lines 204 to 205
export const getUserIsActive = (state: GlobalState, userId: number): boolean =>
[...getUsers(state), ...getCrossRealmBots(state)].some(user => user.user_id === userId);
Copy link
Member

Choose a reason for hiding this comment

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

This creates a case of #3339. :-)

Instead, this should use a cached selector getActiveUsersById, in order to make a nice Map to efficiently look this up in. Adding that can be its own commit coming before this one.

See also this previous chat discussion:
https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/Migrate.20from.20emails.20to.20userIds/near/829640

The new selector can go right next to getActiveUsersByEmail. See also getAllUsersById and getAllUsersByEmail for other examples to compare to.

Comment on lines 109 to 110
"Profile not found": "Profile not found",
"The profile for this user could not be found.": "The profile for this user could not be found.",
Copy link
Member

Choose a reason for hiding this comment

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

This definitely doesn't belong in an NFC commit. :-)

If the error case truly can't happen, then there's no need for a user-facing error message to translate. If it can happen, then this new form of error-handling for it is a user-visible change and therefore definitely not "no functional change".

In general I'm concerned that there's kind of a lot of complexity happening in this commit. Some of it, I think needn't be added at all; other parts may be best split into multiple commits. The change we're ultimately making here is of a kind that should be possible to do in a series where each commit is quite simple and straightforward.

@@ -71,6 +95,12 @@ class AccountDetailsScreen extends PureComponent<Props> {
}

export default connect<SelectorProps, _, _>((state, props) => ({
user: getAccountDetailsUserForEmail(state, props.navigation.state.params.email),
Copy link
Member

Choose a reason for hiding this comment

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

So in the chat discussion we had about this, I recommended:

Then I'd also replace the use of getAccountDetailsUserForEmail (in AccountDetailsScreen) with getAllUsersByEmail, as recommended in the former function's jsdoc.

One quick thing that would help simplify this series is: this change should be its own commit. I didn't specifically mention a separate commit, sorry -- but that's what I meant by "Then I'd also ...". In general when I think of some code changes in terms like "do A; then do B; then do C", what I mean is that A and B and C are each separate commits. This is basically part of how we write clear and coherent commits 🙂

Copy link
Member Author

@agrawal-d agrawal-d Mar 23, 2020

Choose a reason for hiding this comment

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

Okay. The latest push creates some new selectors which are used here - these selectors are in their own, separate commits.

Copy link
Member

Choose a reason for hiding this comment

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

Cool. The change that actually replaces this use of getAccountDetailsUserForEmail should still be its own commit. 🙂

To see why, take a look at the implementation of that function -- there are some funny wrinkles in its behavior, involving the NULL_USER object. Replacing it will change the behavior in those edge cases, if those edge cases are possible. So I want that change as its own small commit, in order to keep a small circle drawn around the task of reading it and thinking through what changed and making sure all loose threads are taken care of.

I think that commit will come before the rest of the big "migrate from email to user ID" change. Probably it should use getUserForEmail.

Copy link
Member Author

@agrawal-d agrawal-d Mar 24, 2020

Choose a reason for hiding this comment

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

Yeah, this makes sense - a migration commit should be a NFC. If it's not, then it's not strictly a migration.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, I'd say that at least for the term "refactor" -- a change that isn't NFC isn't purely a refactor, and "pure refactor" is a close synonym of "NFC".

I'd actually use "migration" somewhat more broadly: e.g. I might talk about "migrating" to a new major version of some dependency (especially a big dependency, like RN itself), and that's typically not NFC. But in this case, the kind of migration we're going for is pretty much a pure refactor.

... Oh, actually, here's another example of "migrate" -- for the commit that replaces the use of getAccountDetailsUserForEmail with getUserForEmail, I would actually describe that as migrating from getAccountDetailsUserForEmail to getUserForEmail. (I noticed this example as I found myself typing exactly that in the comment below.) Even though, in edge cases, it may not behave quite the same, and so it isn't NFC.

Copy link
Member

Choose a reason for hiding this comment

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

So, I think this sequence of code changes is now good!

There's some work I have to do to get to that conclusion, in the case of the change that migrates from getAccountDetailsUserForEmail to getUserForEmail: ask the questions, "what are the cases where this can behave differently? what's the old behavior in those cases, and what's the new behavior? could the new behavior be worse?", and study the code to answer them.

So the commit message should reflect that work. 🙂 I'll edit it to add that.

componentDidMount() {
const { user } = this.props;
if (user === undefined) {
logging.error('User object passed is undefined.');
Copy link
Member

Choose a reason for hiding this comment

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

Ah, perhaps you're thinking of #3809? Just happened across that thread by chance.

Comment on lines 46 to 69
class AccountDetailsScreen extends PureComponent<Props> {
handleChatPress = () => {
const { user, dispatch } = this.props;
dispatch(doNarrow(privateNarrow(user.email)));
componentDidMount() {
const { user } = this.props;
if (user === undefined) {
logging.error('User object passed is undefined.');
}
}

handleChatPress = (email: string) => {
const { dispatch } = this.props;
dispatch(doNarrow(privateNarrow(email)));
};

render() {
const { user, isActive } = this.props;

if (user === undefined) {
return (
<Screen title="Profile not found" style={styles.userNotFoundScreen}>
<Label text="The profile for this user could not be found." />
</Screen>
);
}

Copy link
Member

Choose a reason for hiding this comment

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

So, one thing that's contributing quite a bit of the complexity of the code in this PR is the way it's handling the possibility of being asked to show a user that doesn't actually exist. Specifically, there's about four different spots in this component's code that deal with props.user potentially being undefined instead of a real User object, in addition to the places here and elsewhere dealing with potentially even the userId navigation parameter being undefined.

When lots of different bits of code all try to respond to the same error condition, it makes it hard to understand what the code is doing either in that error case or in the normal case, and hard to edit or refactor the code. So let's try to make sure this case gets taken care of once, as early as possible, and then all the code downstream of that can just deal with a real User object.

This principle is basically a particular form of the "crunchy shell, soft center" principle.

In particular this means props.user should go on being just UserOrBot, not UserOrBot | void, so that all the code inside the component just deals with a real, valid user object.

Specifically, what I'd like to do right now in this code is:

  • If userId (or email, if applicable in the earlier parts of the branch) doesn't actually find us a user object in our data structures, just throw an error.
    • See getStreamForId for an example of how to do this conveniently. We can add a similar selector getUserForId.
  • Similarly, over in InfoNavButtonPrivate, if the given user doesn't actually exist in our data then just throw an error.
    • A selector getUserForEmail, on the same lines as above, may be helpful for this.

Throwing an error is appropriate because I believe it's the case that if we actually have an invalid user ID or email here, that's a bug in the app.

If it later turns out that there is a legitimate case where that can happen -- or if such a bug does exist, and we don't simply debug and fix it, and it's happening with some frequency -- then we can add a bit more complexity to give a nice user-facing error like this "user not found" screen. If we do that, the key principle above will still be an important one: only one spot of code should have to handle the error case, letting everything else deal only with real, valid data.

  • One likely way to do that would be to have a little wrapper component, which shows either the "not found" screen or the actual account-details screen. The wrapper's user prop would be potentially undefined, but the inner component's user prop would not be.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for the detailed explanations! I too felt the changes were getting messy.

@agrawal-d
Copy link
Member Author

Thanks for the review!
@ray-kraesig @gnprice I've pushed some changes. Please review.

@agrawal-d agrawal-d force-pushed the userid branch 4 times, most recently from 9fc8875 to 31d2e67 Compare March 23, 2020 18:57
Copy link
Member

@gnprice gnprice left a comment

Choose a reason for hiding this comment

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

Thanks for the revision! This is getting close.

A few more comments below. With those changes, I think the main "migrate from email to user ID" commit will be a very clean NFC change -- it'll still touch about 12 different files, but all in nice boring regular ways.

@@ -71,6 +95,12 @@ class AccountDetailsScreen extends PureComponent<Props> {
}

export default connect<SelectorProps, _, _>((state, props) => ({
user: getAccountDetailsUserForEmail(state, props.navigation.state.params.email),
Copy link
Member

Choose a reason for hiding this comment

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

Cool. The change that actually replaces this use of getAccountDetailsUserForEmail should still be its own commit. 🙂

To see why, take a look at the implementation of that function -- there are some funny wrinkles in its behavior, involving the NULL_USER object. Replacing it will change the behavior in those edge cases, if those edge cases are possible. So I want that change as its own small commit, in order to keep a small circle drawn around the task of reading it and thinking through what changed and making sure all loose threads are taken care of.

I think that commit will come before the rest of the big "migrate from email to user ID" change. Probably it should use getUserForEmail.

Comment on lines 13 to 15
|}>;
type Props = $ReadOnly<{|
Copy link
Member

Choose a reason for hiding this comment

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

nit: blank line

Copy link
Member Author

Choose a reason for hiding this comment

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

I use an extension called 'Git Lens' that shows the Git blame. Here, it showed the authors between line 13 and 14, so it appeared as if I had inserted a new line. :/

@@ -116,7 +116,8 @@ $!${divOpenHtml}
`;
}

const { sender_full_name, sender_email } = message;
const { sender_full_name } = message;
const sender_id = message.isOutbox ? backgroundData.ownUser.user_id : message.sender_id;
Copy link
Member

Choose a reason for hiding this comment

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

Huh, is there no sender_id in Outbox?

... Huh, indeed there isn't. Well, that's our own code's fault. 😝 We should fix that.

This code is a good workaround for this PR's purposes, because fixing that omission is made complex by the fact we store outbox messages between runs. I'll file an issue for it.

Copy link
Member

Choose a reason for hiding this comment

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

Comment on lines 203 to 206
const userId = parseInt(event.fromUserId, 10);
if (Number.isNaN(userId)) {
logging.error("WebViewEventHandlers: Failed to parse 'fromUserId' string to integer", {
fromUserId: event.fromUserId,
});
showToast(_('The profile for this user could not be found.'));
} else {
Copy link
Member

Choose a reason for hiding this comment

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

If this error condition ever happens, it's a bug purely internal to our code, and it's not one that even relies on any subtle invariants of our big data structures -- there's a handful of steps from our templates, via the click handler in js.js, to here, and this bug would mean one of those somehow fumbled the data.

The usual way we handle that kind of potential bug is by just throwing an exception. I think that's the most appropriate thing to do here, too. As you can see, trying to do this style of more-verbose error handling makes for quite a bit more code to read 😉

(This style is exactly the right kind of approach when parsing data we get from the network! But here we're inside the "soft center" of the app, and so we should take advantage of that.)

@agrawal-d
Copy link
Member Author

agrawal-d commented Mar 24, 2020

@gnprice thanks for the review.

I've pushed some changes. I've also reworded the big commit 'migrate from email to user ID' as NFC again, I hope that it qualifies now.

Please re-review.

Copy link
Member

@gnprice gnprice left a comment

Choose a reason for hiding this comment

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

Thanks @agrawal-d for the revisions!

This looks great. A few comments below; I plan to fix those remaining things up and merge.

} else {
props.dispatch(navigateToAccountDetails(userId));
}
Copy link
Member

Choose a reason for hiding this comment

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

nit: delete else { and }, to keep the main codepath in a single linear narrative without added indentation

Comment on lines +203 to +205
const userId = parseInt(event.fromUserId, 10);
if (Number.isNaN(userId)) {
throw new Error(`Failed to parse fromUserId string '${event.fromUserId}' to integer`);
Copy link
Member

Choose a reason for hiding this comment

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

A further small improvement here would be to push this a step further back: if fromUserId is a string that doesn't parse, that's already invalid, so make it fromUserId: number instead, and do the parsing (and throwing) in js.js when this object is constructed.

See also requireAttribute, and dd03939 which added it. Cleanest might be to make a helper named like requireAttributeNumber, which calls requireAttribute and then parseInt, and returns a number or throws.

Copy link
Member Author

Choose a reason for hiding this comment

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

Will do this in the prep commits of #3878.

Comment on lines 202 to 204
* In particular, returns the user for:
* * i) cross-realm bots
* * ii) deactivated users (`is_active` false; see `User` and the linked docs)
Copy link
Member

Choose a reason for hiding this comment

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

I want to word this part a bit differently; I'll do that as an added commit on top.

Two concrete things I notice:

  • "returns the user for cross-realm bots [and] deactivated users" is kind of confusing; it sounds like other users (the common case) aren't included, or maybe like it returns a user that could be called "the user for" a cross-realm bot.
  • "See also" is a great technique to provide more context 🙂 See e.g. some mentions in the Flutter style guide (which in general is great, and especially great about docs style), here and here.

Copy link
Member Author

@agrawal-d agrawal-d Mar 26, 2020

Choose a reason for hiding this comment

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

I saw a01decc, it's more clear now.

The Flutter guide is useful - thanks! This may be off-topic, but how did you end up reading the Flutter style guide in such depth?

Copy link
Member

Choose a reason for hiding this comment

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

how did you end up reading the Flutter style guide in such depth?

A couple of years ago when I first started doing mobile development, Flutter had just entered beta and looked pretty cool, so I spent some time working with it. One of the things I was impressed by was the quality of the docs, and relatedly of the code and APIs. Here's a past chat thread with a few thoughts on Flutter: thread.

@@ -31,7 +30,7 @@ type SelectorProps = $ReadOnly<{|
|}>;

type Props = $ReadOnly<{|
navigation: NavigationScreenProp<{ params: {| email: string |} }>,
navigation: NavigationScreenProp<{ params: {| userId: number |} }>,
Copy link
Member

Choose a reason for hiding this comment

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

nit in commit message: rather than navigateToAccountDetails as prefix, I think account details screen or AccountDetailsScreen is closer to the heart of it. The navigateToAccountDetails function is a helper to get to this screen.

@@ -71,6 +95,12 @@ class AccountDetailsScreen extends PureComponent<Props> {
}

export default connect<SelectorProps, _, _>((state, props) => ({
user: getAccountDetailsUserForEmail(state, props.navigation.state.params.email),
Copy link
Member

Choose a reason for hiding this comment

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

Yeah, I'd say that at least for the term "refactor" -- a change that isn't NFC isn't purely a refactor, and "pure refactor" is a close synonym of "NFC".

I'd actually use "migration" somewhat more broadly: e.g. I might talk about "migrating" to a new major version of some dependency (especially a big dependency, like RN itself), and that's typically not NFC. But in this case, the kind of migration we're going for is pretty much a pure refactor.

... Oh, actually, here's another example of "migrate" -- for the commit that replaces the use of getAccountDetailsUserForEmail with getUserForEmail, I would actually describe that as migrating from getAccountDetailsUserForEmail to getUserForEmail. (I noticed this example as I found myself typing exactly that in the comment below.) Even though, in edge cases, it may not behave quite the same, and so it isn't NFC.

@@ -71,6 +95,12 @@ class AccountDetailsScreen extends PureComponent<Props> {
}

export default connect<SelectorProps, _, _>((state, props) => ({
user: getAccountDetailsUserForEmail(state, props.navigation.state.params.email),
Copy link
Member

Choose a reason for hiding this comment

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

So, I think this sequence of code changes is now good!

There's some work I have to do to get to that conclusion, in the case of the change that migrates from getAccountDetailsUserForEmail to getUserForEmail: ask the questions, "what are the cases where this can behave differently? what's the old behavior in those cases, and what's the new behavior? could the new behavior be worse?", and study the code to answer them.

So the commit message should reflect that work. 🙂 I'll edit it to add that.

We are not caching any real work here. Part of zulip#3927.
Create a selector that returns a user object for a given user ID.
Throws if no user exists with for the given ID.
Create a selector that returns a user object for a given email.
Throws if no user exists with for the given email.
gnprice and others added 7 commits March 25, 2020 17:26
Most notably, use "see also getAllUsers" to point to details on the
quirks of who counts as a user.
Currently, AccountDetailsScreen uses 'getAccountDetailsUserForEmail'
to find the user object for the given email. But this selector is
deprecated - it involves the use of NULL_USER. Replace it with
the new selector 'getUserForEmail' instead.

This can behave differently in the case where the given email
doesn't belong to any known user.  The existing behavior is we'll
show the screen with some dummy data -- like showing the email as
the user's name; trying to use the `realm_uri` as their avatar URL;
and skipping presence information.

That's a pretty broken-looking experience if it can happen; and if
it can happen, it's a bug in the app.  (There's one known case that
can cause it, which is if the user in question changes their email
address.  This is basically part of why zulip#3764 is a bug.)  Instead of
showing some broken-looking bogus data, better to just show nothing.
The simplest version of that is to throw an error; we could do a
softer version of "show nothing", but because this case should be
rare the simple thing is fine for now.

[greg: expanded commit message]
The previous commit eliminated the one call site of this selector.
Now we get to delete it!
We are moving towards user IDs to identify users.  This commit
refactors AccountDetailsScreen to take a user ID in its nav params,
all call sites of navigateToAccoutDetails to pass a user ID, and makes
other small changes required for that.

Part of zulip#3764.
'getActiveUsersById' returns all users except those which have been
deactivated as map with user ID as the key. This slector will be
used in 'getUserIsActive',as a map offers a better performance than
linear scans over arrays, and these values will also be cached.

See zulip#3339 for
discussion.
We are migrating the codebase from emails to user IDs to identify
users, because user IDs are more reliable. This commit also
modifies the selector to use the new selector 'getActiveUsersById'.
This will hopefully help us use user IDs instead of emails everywhere
we can easily choose, as part of using user IDs everywhere, i.e. zulip#3764.
@gnprice gnprice merged commit 680489f into zulip:master Mar 26, 2020
@gnprice
Copy link
Member

gnprice commented Mar 26, 2020

OK -- merged! Thanks again @agrawal-d .

I made the adjustments mentioned in my last round of comments above, and added a couple of extra commits -- take a look:

3750bc5 user [nfc]: Drop caching for 'getUserIsActive'.
95d5e82 user [nfc]: Create selector 'getUserForId'.
03016a9 user [nfc]: Create selector 'getUserForEmail'.
a01decc user [nfc]: Tweak jsdoc on new selectors getUserFor{Id,Email}.
12a9935 account details: Use 'getUserForEmail' to retrieve user.
98b1b06 user [nfc]: Delete deprecated, disused getAccountDetailsUserForEmail.
239c75c account details [nfc]: Migrate from email to user ID.
28dbb99 user [nfc]: Create selector 'getActiveUsersById'.
7a44328 user [nfc]: Refactor 'getUserIsActive' to accept user ID.
680489f user [nfc]: Point to user-ID versions in docs of email-using selectors.

One other small style change, in commit messages: I shortened the prefixes on the summary lines. For example the word "selector" is right there in the later part of most of the summary lines 🙂 But also more broadly the prefix doesn't have to pinpoint the file involved, only the area or subsystem of the code. Pinpointing the file is what git log --stat (or equivalent) is for 😉

@gnprice
Copy link
Member

gnprice commented Mar 26, 2020

I made the adjustments mentioned in my last round of comments above

Oh, except for this one: #3968 (comment)

That would be a nice (and small/quick) followup to do, I think.

@gnprice
Copy link
Member

gnprice commented Mar 26, 2020

#3968 (comment)

That would be a nice (and small/quick) followup to do, I think.

In fact, looking over at #3878 which originally led to this PR, I think this followup would be helpful there too! You can use the same requireAttributeNumber (maybe requireNumberAttribute? requireNumericAttribute? my original suggestion kind of sounds like it's about something called an "attribute number") helper there, too, in the same way.

@agrawal-d agrawal-d deleted the userid branch March 26, 2020 02:38
@agrawal-d
Copy link
Member Author

agrawal-d commented Mar 26, 2020

In fact, looking over at #3878 which originally led to this PR, I think this followup would be helpful there too! You can use the same requireAttributeNumber (maybe requireNumberAttribute? requireNumericAttribute? my original suggestion kind of sounds like it's about something called an "attribute number") helper there, too, in the same way.

Will do this in the prep commits of #3878. requireNumericAttribute seems better.

gnprice added a commit that referenced this pull request Oct 18, 2021
This property is required (since 60847c9 / #4667, earlier this
year), so we can rely on it to simplify away this workaround.

This workaround was in fact the original prompt that caused me to
notice we didn't have sender_id (or stream_id) on Outbox, and file
  #3968 (comment)
gnprice added a commit to gnprice/zulip-mobile that referenced this pull request Oct 19, 2021
This property is required (since 60847c9 / zulip#4667, earlier this
year), so we can rely on it to simplify away this workaround.

This workaround was in fact the original prompt that caused me to
notice we didn't have sender_id (or stream_id) on Outbox, and file
  zulip#3968 (comment)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants