-
-
Notifications
You must be signed in to change notification settings - Fork 668
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
Display presence status taking away status into account #3326
Conversation
#3263 is now merged, which I believe unblocks this one! |
ac0ae35
to
fe6d841
Compare
This is ready for review. |
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.
Thanks @borisyankov !
This looks generally good. I quite like the simplification sweeping through in the second commit!
Specific comments below.
src/users/userSelectors.js
Outdated
users.reduce((usersById, user) => { | ||
usersById[user.email] = user; | ||
return usersById; | ||
}, ({ ...undefined }: UserEmailMap)), |
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.
Huh interesting -- why this instead of {}
?
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.
This fixes: "Cannot cast object literal to UserEmailMap because inexact object literal [1] is incompatible with exact UserEmailMap [2]."
Because {}
is considered 'unsealed object'
https://flow.org/en/docs/types/objects/#toc-unsealed-objects
A concept in Flow that might be removed as not very intuitive.
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.
Huh fascinating!
Ah, I see you've made UserEmailMap
exact. I've shied away from making many types with "indexer properties" exact, because I keep getting strange errors from them. This may be a very useful workaround for that problem!
Let's be sure to mention that either in a comment and/or the commit message. Also you mentioned in audio chat that you found this workaround in an issue thread on Flow. That link will be super useful to have written down.
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.
bump:
Also you mentioned in audio chat that you found this workaround in an issue thread on Flow. That link will be super useful to have written down.
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.
The exact issue I found was:
facebook/flow#7343
This does not have any extra information that is worth linking to, besides the fact it named the problem (unsealed objects) which made me read the docs.
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.
There are other GitHub issues that discuss sealed/unsealed objects, like:
facebook/flow#2977
I don't think linking them directly is of any specific benefit since the main benefit is identifying the cause and knowing what term to search for.
src/users/userSelectors.js
Outdated
@@ -41,6 +41,13 @@ export const getAllUsersByEmail = createSelector(getAllUsers, allUsers => | |||
}, {}), | |||
); | |||
|
|||
export const getUsersByEmail: Selector<UserEmailMap> = createSelector(getUsers, (users = []) => | |||
users.reduce((usersById, user) => { |
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.
I think I've mentioned before that I'm not a fan of this pattern where reduce
is used but the callback actually just mutates the object -- I think they're almost always clearer as a straightforward loop. Because it's just like the code around it, though, I won't press that here; maybe I'll follow up separately with a sweep through to change a lot of these.
src/users/userSelectors.js
Outdated
@@ -41,6 +41,13 @@ export const getAllUsersByEmail = createSelector(getAllUsers, allUsers => | |||
}, {}), | |||
); | |||
|
|||
export const getUsersByEmail: Selector<UserEmailMap> = createSelector(getUsers, (users = []) => | |||
users.reduce((usersById, user) => { | |||
usersById[user.email] = user; |
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.
This name definitely needs to be made accurate, though 😉
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.
bump
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.
bump
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.
Fixed.
src/users/userSelectors.js
Outdated
@@ -41,6 +41,13 @@ export const getAllUsersByEmail = createSelector(getAllUsers, allUsers => | |||
}, {}), | |||
); | |||
|
|||
export const getUsersByEmail: Selector<UserEmailMap> = createSelector(getUsers, (users = []) => |
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.
The commit message here says:
We should not use the existing `getAllUsersByEmail` since it returns
all users and realm bots and messes up our strict typing.
Two questions:
- We should not use that selector where? From context I think you probably mean in the upcoming change to PresenceStatusIndicator; that's fine but should be explicit, like "In an upcoming commit, we'll want to use this instead of the existing
getAllUsersByEmail
because the latter [...]".- Remember the context of the next few commits isn't always handy or obvious, when this is found in the history in the future.
- Without that, it sounds like you're saying we should never use
getAllUsersByEmail
. Maybe that is what you're saying? I'm not actually 100% sure.
- Why not use
getAllUsersByEmail
in that upcoming commit? This new one amounts to a filtered view of that one, with a subset of the entries. Sure, it seems plausible that the difference will never matter... but if it ever does (we're showing presence status for a "user" that doesn't show up ingetUsers
), it seems like we want to go ahead and use the data. And that's the simpler thing, too.
More broadly, I definitely agree that these should be made clearer. I think the most important bit will be to work out the differences in what they actually mean... and write that down as a sentence or two each, in jsdoc. I'm not entirely sure what the contrasts are myself. Then we can find names that reflect those contrasts.
There are some clues in the commit messages in this file's history -- take a look at git log --stat -p src/users/userSelectors.js
.
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.
bump
@@ -2,9 +2,12 @@ | |||
/* eslint-disable react-native/no-unused-styles */ | |||
import React, { PureComponent } from 'react'; | |||
import { StyleSheet, View } from 'react-native'; | |||
import { connect } from 'react-redux'; |
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.
This refactoring makes sense to me! Thanks also for the clear commit messages explaining why ❤️
@@ -25,9 +28,16 @@ const styles = StyleSheet.create({ | |||
}, | |||
}); | |||
|
|||
type PropsFromState = {| |
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.
Ah, interesting idea to split this out! I can think of some other components that would likely benefit from a similar change.
I'm not keen on using the word "state" here -- the thing it first makes me think of in this context (juxtaposed so closely with Props
) is the React component's state, which is not at all what you mean. How about PropsFromRedux
?
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.
Or perhaps PropsFromConnect
?
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.
I was playing with ConnectedProps
but sounded too cutesy and not clear.
PropsFromConnect
sounds good. Did rename.
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.
Makes sense!
I think I've sometimes said "connected props" and spelled it "connect
ed props"... but without the punctuation it does become unclear.
@@ -38,18 +48,28 @@ type Props = {| | |||
* * gray if 'offline' | |||
* | |||
* @prop [style] - Style object for additional customization. | |||
* @prop [presence] - UserPresence object used to determine the status from. | |||
* @prop [email] - email of the user whose status we are showing. |
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.
This is no longer optional, right?
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.
Right. Fixed.
|
||
const awayStatus = userStatus[user.user_id]; | ||
|
||
const status = awayStatus && awayStatus.away ? 'offline' : statusFromPresence(userPresence); |
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.
Hmm. I think I'd rather this logic be encapsulated in its own function or method somewhere.
A minimal change would be to put it in a method on the component -- so like
render() {
const status = this.getStatus();
if (status === null) {
return null;
}
const { style, hideIfOffline } = this.props;
// if (hideIfOffline ... etc., etc.
and that would I think help a lot in separating the logic for the sake of reading and understanding this code.
Ultimately I'd want the substance of that method, especially the awayStatus.away ? 'offline' : statusFromPresence(...)
part, to be in a selector or similar function somewhere, outside of display code. But I'm OK merging without that.
cabd599
to
c76f7d2
Compare
Did the discussed changes. Ready for a merge? |
7ef7abe
to
9734141
Compare
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.
Thanks @borisyankov for these updates!
Several of my comments seem to still be open, mainly on the first commit adding getUsersByEmail
.
I think I can merge the refactor, though -- that will take a lot of the complexity out of the branch.
src/users/userSelectors.js
Outdated
@@ -41,6 +41,13 @@ export const getAllUsersByEmail = createSelector(getAllUsers, allUsers => | |||
}, {}), | |||
); | |||
|
|||
export const getUsersByEmail: Selector<UserEmailMap> = createSelector(getUsers, (users = []) => |
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.
bump
src/users/userSelectors.js
Outdated
@@ -41,6 +41,13 @@ export const getAllUsersByEmail = createSelector(getAllUsers, allUsers => | |||
}, {}), | |||
); | |||
|
|||
export const getUsersByEmail: Selector<UserEmailMap> = createSelector(getUsers, (users = []) => | |||
users.reduce((usersById, user) => { | |||
usersById[user.email] = user; |
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.
bump
src/users/userSelectors.js
Outdated
users.reduce((usersById, user) => { | ||
usersById[user.email] = user; | ||
return usersById; | ||
}, ({ ...undefined }: UserEmailMap)), |
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.
bump:
Also you mentioned in audio chat that you found this workaround in an issue thread on Flow. That link will be super useful to have written down.
return null; | ||
} | ||
|
||
const status = statusFromPresence(presence); | ||
const status = statusFromPresenceAndUserStatus(userPresence, userStatus[user.user_id]); |
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.
Cool, this nicely encapsulates the business logic (such an odd term here; "product logic"?) at work here.
A further improvement would be to also stuff the next few lines above it, with user = usersByEmail[email]
etc., inside. I think the ideal interface for that is probably to put this all inside a selector... e.g. the connect
call could say
presenceStatus: getPresenceStatusForEmail(state, props.email),
That's a pattern we don't currently use much, if at all, but there are a lot of places in our code that would benefit. Detailed discussion at #3015 .
I'm fine merging with this interface as it is, though.
9734141
to
6037f7c
Compare
Now `connect`s to the `userStatus` state and if `away` for the given user exists and is set to `true`, we override the calculated value and make it `offline` instead. It is unfortunate that there is a (temporary) discrepancy between our `presence` and `userStatus` key used for mapping (`email` vs `user_id`) thus needing the `usersByEmail` state too. We should be moving `presence` to 'id to presence mapping' and then we would replace the `email` prop with `userId` prop. Later.
6037f7c
to
db719ac
Compare
Rebased and now using the |
Merged -- thanks again! I'd still be glad to see the followup I mentioned in this comment:
|
Update all presence UI to correctly, taking into account away status.