Skip to content
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

Away status #3263

Merged
merged 4 commits into from
Feb 6, 2019
Merged

Away status #3263

merged 4 commits into from
Feb 6, 2019

Conversation

borisyankov
Copy link
Contributor

@borisyankov borisyankov commented Jan 10, 2019

Implements #3266

@borisyankov
Copy link
Contributor Author

While the PR itself is WIP (waiting for clarification and discussion on some back-end functionality) all three current commits can be merged already.

@borisyankov borisyankov changed the title WIP: Away status Away status Jan 11, 2019
@gnprice
Copy link
Member

gnprice commented Jan 14, 2019

Thanks @borisyankov !

There are some rebase conflicts with the refactors to action and event types that I made last week. I think all of the actual conflicts will be easy to resolve.

One thing that would be great is in the commit that does this:

+export type EventUserStatusUpdateAction = {|
+  ...ServerEvent,
+  type: typeof EVENT_USER_STATUS_UPDATE,
+  away: boolean,
+  user_id: number,
+|};
+

let's use the new src/api/eventTypes.js to write down exactly what the server actually sends in these events. Then the action type can refer to that -- much like EventPresenceAction does now.

@gnprice
Copy link
Member

gnprice commented Jan 14, 2019

(In general, whenever we're adding a new event type -- or for another reason doing the work of nailing down exactly what the server sends in a given type of event -- I'd like to use the new eventTypes.js to write that information down while it's fresh.)

@gnprice gnprice added P1 high-priority and removed P0 critical Highest priority labels Jan 15, 2019
@borisyankov borisyankov added P0 critical Highest priority and removed P1 high-priority labels Jan 24, 2019
@gnprice
Copy link
Member

gnprice commented Jan 29, 2019

(It looks like my comments above about eventTypes.js still apply.)

};

export default (auth: Auth, params: SetAwayStatusParams): Promise<ApiResponseSuccess> =>
apiPost(auth, 'users/me/status', objectToParams(params));
Copy link
Member

Choose a reason for hiding this comment

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

What's the reasoning for using objectToParams here?

I believe it should have no effect at all -- neither of these properties are permitted to be an array, or undefined. So it seems simpler to just say params.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The function is used for its ability to remove keys with undefined values.

Copy link
Member

Choose a reason for hiding this comment

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

This isn't much of a reply. I wrote above:

I believe it should have no effect at all -- neither of these properties are permitted to be an array, or undefined.

If that's mistaken, would you explain why? If not, why do you take steps to remove keys with undefined values?

Copy link
Member

Choose a reason for hiding this comment

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

Aha, I see in a later commit that you do pass it params objects that have undefined values. I was confused to see that Flow accepted that; it turns out that the documented semantics of ?: properties is like so:

In addition to their set value type, these optional properties can either be void or omitted altogether. However, they cannot be null.

So indeed this is doing something useful, then.

(This reply remains not much of a reply.)

@borisyankov borisyankov force-pushed the away-status branch 11 times, most recently from 07f4d87 to 926c6ce Compare January 31, 2019 15:33
@borisyankov
Copy link
Contributor Author

This is ready for review.

return {
...event,
type: EVENT_USER_STATUS_UPDATE,
};
Copy link
Member

Choose a reason for hiding this comment

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

Take a look at the block of 8 event types up above, with actionTypeOfEventType. This should go there.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, didn't notice these case refactorings.
I am not sure this is an improvement, removing simple duplication by increasing the complexity of the code...
Anyway, did the change.

Copy link
Member

Choose a reason for hiding this comment

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

Cool.

I am not sure this is an improvement, removing simple duplication by increasing the complexity of the code...

I think the main thing I liked about this particular small refactoring was that we had all these different cases which were doing essentially the same thing... but that wasn't obvious because they were scattered around this switch statement and interleaved with cases that worked differently. And so each of them added complexity as you had to look at each case individually to see what it did. Now it's very clear that these all have the exact same boring structure, and the exceptions which do something more interesting stand out better.

There are other ways that could be done. Simply reordering cases to bring these together would have accomplished that and certainly not added any complexity, and would have been fine though remained verbose. When I went to make it less verbose, I chose this particular style largely because... this is what the existing code in the file already does for a bunch of other cases! 🙂

...EventCommon,
user_id: number,
away?: boolean,
status_text?: string,
Copy link
Member

Choose a reason for hiding this comment

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

Huh interesting! Are both of these really optional?

Also: Is your description here based on empirical observation, or reading the server code, or discussions with Steve, or something else?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Based on all three. One or two of them exist. Never none (as expected).

Copy link
Member

Choose a reason for hiding this comment

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

OK, so, a thing that's really important to get here is an idea of what one of these events means.

What does it mean about a user when we get an event with away: true?

What if the previous event for them had a status_text, and now this one doesn't mention status_text -- does that mean they no longer have a status_text? Or does the old one stay in place?

It sounds like you have some idea of what these are supposed to mean! Otherwise it would make no sense at all to say something like "as expected". I really do not know what to expect, because I do not know what these events mean.

I can guess, basically from imagining if I were designing a system related to user away status, what design choices would lead to an interface that looked like this one... but there are multiple possibilities there, and Steve may reasonably have had another one in mind entirely.

It's good that you have some idea of what these are supposed to mean. It is super important that that idea get written down somewhere. I think a bit of jsdoc on this event type might be the ideal place for that.

Copy link
Member

Choose a reason for hiding this comment

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

Ah cool, I see that the UserStatus types in the app's own data structures now has some quite helpful jsdoc, and of course that overlaps with this information.

No need to duplicate info. Any given piece of information can be written down in one of the places where it's important, and then write "See also UserStatus in src/types.js" or whatever in each of the other places.

I think the key bit of info that that doesn't cover and is important for this event type is: what does it mean when one of these is missing? Also away: false, which can't occur in the other place.

(Ideally I think the information on that UserStatus type would be in api/ somewhere -- particularly as we seem to use it in InitialData, which is really mainly a matter of describing the API. But it's fine to defer doing that rearrangement.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed. I had a commit somewhere that I think I dropped because it was causing too many merge conflicts that moved InitialData and all related types to apiTypes.

};

export default (auth: Auth, params: SetAwayStatusParams): Promise<ApiResponseSuccess> =>
apiPost(auth, 'users/me/status', objectToParams(params));
Copy link
Member

Choose a reason for hiding this comment

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

Aha, I see in a later commit that you do pass it params objects that have undefined values. I was confused to see that Flow accepted that; it turns out that the documented semantics of ?: properties is like so:

In addition to their set value type, these optional properties can either be void or omitted altogether. However, they cannot be null.

So indeed this is doing something useful, then.

(This reply remains not much of a reply.)

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 @borisyankov for this update!

Looks good at a high level; comments on specifics below.

(I'm not quite done looking at the reducer, in the interesting case with EVENT_USER_STATUS_UPDATE; breaking now for lunch so sending what I have, but might have another thought or two on that bit later.)

@@ -110,6 +110,13 @@ export type PresenceEvent = {|
presence: UserPresence,
|};

export type UserStatusEvent = {|
Copy link
Member

Choose a reason for hiding this comment

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

This also needs the type property -- that's a pretty important one 🙂

Copy link
Member

Choose a reason for hiding this comment

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

Cool, I see this is done in an updated version of the branch. Small thing there -- the pattern I've set in this EventTypes enum is for the names to exactly match the values:

  static update_message_flags: 'update_message_flags' = 'update_message_flags';

I'd like to continue that pattern here, so EventTypes.user_status.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated, haven't noticed that.
In lights of this, I think this is just a way more verbose version of the type:
update_message_flags | user_status
which will also not allow us to specify an invalid value and hopefully will provide auto-suggest as well.

Copy link
Member

Choose a reason for hiding this comment

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

Hmm, I don't think I'm quite picturing what alternative code you have in mind. Want to send a small PR to demonstrate? Happy to experiment with other ways of writing this.

src/types.js Outdated
@@ -706,6 +718,10 @@ export type InitialDataUpdateMessageFlags = {|
},
|};

export type InitialDataUserStatus = {|
user_status: UserStatusMap,
Copy link
Member

Choose a reason for hiding this comment

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

The commit message for this says:

Pass `user_status` to `fetch_event_types` to receive user status
data on initial load. The data itself is in `away_user_ids` array.

I don't see an away_user_ids, though. Was that perhaps a previous version of this API?

In any case, the commit message and the code should agree.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I did update the commit message and made the user_status value optional to reflect that it might not exist.

Copy link
Member

Choose a reason for hiding this comment

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

Thanks! I appreciate the jsdoc on the UserStatus type, too.

FWIW my inclination would be to leave the description of user_status out of the commit message, as it's covered quite nicely in the jsdoc.

} from '../actionConstants';
import { NULL_OBJECT } from '../nullObjects';

const initialState: UserStatusState = NULL_OBJECT;
Copy link
Member

Choose a reason for hiding this comment

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

This isn't a place where we benefit from the singleton-ness of NULL_OBJECT -- there's never a reason to ===-compare one of these states with some other unrelated object that might happen to be empty. So the simpler {} is preferable.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Completely disagree because:

  • we are currently using this pattern everywhere
  • there are comparisons happening, any selector that uses that or any component that uses that as props does a comparison against the old value
  • as a bonus this object is frozen and will throw if we mutate it, revealing a potential bug

Copy link
Member

Choose a reason for hiding this comment

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

there are comparisons happening, any selector that uses that or any component that uses that as props does a comparison against the old value

Right, but that's perfectly well served by const initialState: UserStatusState = {};.

(That's why I wrote "with some other unrelated object that might happen to be empty".)

If there's ever a place where we compare one of these with a value that comes from some other mention of NULL_OBJECT -- like some other reducer for a different part of the state tree -- that would indicate a very worrying bug.

we are currently using this pattern everywhere

Hmm, true:

$ perl -lne 'print $1 if (/^const initialState.* = (.*)/)' src/*/*Reducer*.js | sort | uniq -c
      5 {
      1 getStateForRoute('loading') || NULL_NAV_STATE;
     12 NULL_ARRAY;
      9 NULL_OBJECT;

So shrug I guess it's fine for this PR to go along with the pattern, then. I might go through at some point and see about changing them all together, though.

Copy link
Member

Choose a reason for hiding this comment

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

as a bonus this object is frozen and will throw if we mutate it, revealing a potential bug

This is certainly something that could be useful in general!

I am not sure there is much value, though, in only having it on the empty values. I'd rate it a lot more likely to catch bugs if we were to do it systematically, on all the objects and arrays in our Redux state trees. The Reselect cached selector values would also be a good target.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I suspect the operation on deep object is slow as Object.freeze() does not work deeply and the deepFreeze we use in the unit-tests is iterating children calling Object.freeze() on them if array or object.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It seems the source code for that is shorter than an explanation:
https://github.com/substack/deep-freeze/blob/master/index.js

Copy link
Member

Choose a reason for hiding this comment

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

It seems the source code for that is shorter than an explanation

Like so much of the JS library ecosystem. 😝 Thanks for the link!

Yeah, that implementation may not be a suitable one for normal use. I think a good version of this would probably have to look something like: freeze each object/array as we create it, and count on its property values having been respectively frozen when they were created, without trying to iterate through them to double-check that.

Then the one potentially tricky bit is finding a way to spell that that doesn't make all our reducers (and cached selectors) annoyingly verbose.

@@ -54,6 +55,8 @@ export const getTopics = (state: GlobalState): TopicsState => state.topics;

export const getUserGroups = (state: GlobalState): UserGroup[] => state.userGroups;

export const getUserStatus = (state: GlobalState): UserStatusState => state.userStatus;
Copy link
Member

Choose a reason for hiding this comment

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

This should go in the same commit that adds this branch to the state tree.

Copy link
Member

Choose a reason for hiding this comment

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

bump

return initialState;

case REALM_INIT:
return action.data.user_status || initialState;
Copy link
Member

Choose a reason for hiding this comment

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

The commit message points out:

Note: Older back-ends do not have a `user_status` key and we handle
this correctly by leaving the state in a valid, empty state.

Quite so! This is an important bit of logic to have.

As we've discussed recently, if it's important enough to have code for, it's important enough to have in the types. The type should look like user_status?: UserStatusMap. Ideally with a line of jsdoc saying what this commit message says: that older servers (older than what version?) don't send that property.

Copy link
Member

Choose a reason for hiding this comment

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

Thanks for adjusting the type!

I'd still like to see this part too:

Ideally with a line of jsdoc saying what this commit message says: that older servers (older than what version?) don't send that property.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't even know what the next version will be called and the current official version does not support it. ¯(ツ)/¯

Copy link
Member

Choose a reason for hiding this comment

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

OK but say what you do know! Even the fact that "older" here means "all existing releases as of today, but not master" is much more informative than just "older" -- just "older" could easily mean "until some release a year or two ago".

The point here is to not make someone in the future have to go do an archaeological investigation, when they just want to learn a fact that is obvious to you right now.

E.g.: "Older servers (up through at least 1.9.1) don't send this property."

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.

And those last few comments. Over to you!

return {
...state,
[action.user_id]: newUserStatus,
};
Copy link
Member

Choose a reason for hiding this comment

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

Hmm, this is all kind of awkwardly verbose.

How about we let the values in our state be undefined rather than absent? Then we can write something like

const status = state[action.user_id] || {};
return {
  ...state,
  [action.user_id]: {
    away: action.away !== undefined ? action.away : status.away,
    status_text: action.status_text !== undefined ? action.status_text : status.status_text,
  },
};

Copy link
Member

Choose a reason for hiding this comment

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

(I don't love that version either, but it feels a little more compact and direct.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This code has some drawbacks.
If previously we hadn't set away or status_text it will now be set to undefined.

I am going in the other direction. The value away will never be false but will miss completely, status_text will never be null, undefined or an empty string with length=0 but will also be deleted.

Copy link
Member

Choose a reason for hiding this comment

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

If previously we hadn't set away or status_text it will now be set to undefined.

Well yes, that is what I said 🙂 :

How about we let the values in our state be undefined rather than absent?

More precisely, that would mean that each of those properties would always be set, whether to undefined or to something else.

I am going in the other direction. The value away will never be false but will miss completely, status_text will never be null, undefined or an empty string with length=0 but will also be deleted.

OK, cool -- I'll look at the resulting code shortly.

Copy link
Member

Choose a reason for hiding this comment

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

OK, now that I see the intended semantics for these (away has just two states, and status_text as empty-string is synonymous with missing), I think probably the cleanest thing would actually be to have a type like this in our Redux state tree: {| away: boolean, status_text: string |}. Then this code would look like

/** The status a user implicitly has when not present in the state. */
export const DEFAULT_STATUS = { away: false, status_text: '' };
// ...

const status = state[action.user_id] || DEFAULT_STATUS;
return {
  ...state,
  [action.user_id]: {
    away: action.away !== undefined ? action.away : status.away,
    status_text: action.status_text !== undefined ? action.status_text : status.status_text,
  },
};

One advantage of this would be that it makes explicit what it means when we don't have this kind of data on a given user -- unlike for many other kinds of data where it would mean "we don't know", here it means "they have these default values". The export is there for a selector to use to encapsulate that fact.

I'm OK merging a version with the existing design, though.

* * when missing the presence is not changed
* * can not be `false`
* @prop status_text - a string representing information the user decided to
* manually set as his 'current status'
Copy link
Member

Choose a reason for hiding this comment

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

(Thanks again for adding this jsdoc!)

A much more specific followup question, now that the overall semantics are laid out here:

Can status_text be present but undefined? What about ''? If either of those is possible, do they mean something different from being absent?

Copy link
Member

Choose a reason for hiding this comment

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

Aha, I see the commit message for the later commit "Handle 'EVENT_USER_STATUS_UPDATE' action" has this information!

Currently the web app does update the `away` value separately from
the `status_text` string and that is the immediate plan for mobile
too. It is possible though that both values are changed at once.

 * if an event contains `away: false` this has the meaning of 'clear
   away overriding'
 * if an event contains `status_text: ''` this means 'clear status text'

There is value in keeping our internal state simpler and consistent
with what the back-end gives us as values. To achieve this we make
sure if we get `away: false` and `status_text: ''` we remove the
corresponding keys.

So it sounds like the answer is:

  • can't be undefined, nor ''
  • instead, will just be absent.

This is essential info for understanding the code around this type. Not only the reducer which maintains it, but also code which consumes it. And -- key test for what belongs in the code + comments, and not only commit message -- it's not at all specific to the change between this commit and its parent, and instead is 100% relevant to understanding the new code from scratch as it is, and for any future version of the codebase where these bits of code looks more or less like as they do here.

So let's stick that in the jsdoc too.

(There's also some information in there answering my questions about the semantics of the events. The same thing applies.)

};

/**
* Specifies user status related properties
* @prop away - present if we are to override user's presence status
Copy link
Member

Choose a reason for hiding this comment

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

This line strikes me as a good cue to add a "See also" pointing at where we discuss presence... and an even better cue to add a "See also" there pointing at here, because this feature is kind of action-at-a-distance upon presence and could be surprising if you're looking at some presence behavior and don't know to look at this area.

The UserPresence type looks like a good main entry point to point at. Looks like that's currently in api/eventTypes.js. (It probably doesn't ultimately belong quite there, but certainly somewhere under api/. Anyway, the "see also" only needs to mention it by name.)

@@ -17,7 +17,7 @@ import type { Message, Stream } from './apiTypes';
//

/** See ClientPresence, and the doc linked there. */
export type UserStatus = 'active' | 'idle' | 'offline';
export type PresenceStatus = 'active' | 'idle' | 'offline';
Copy link
Member

Choose a reason for hiding this comment

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

This is a good rename! I think in fact it belongs at the very beginning of the branch -- then that properly clears things up for starting to use the term "user status" for this new feature.

Ditto the other related rename.

return initialState;

case REALM_INIT:
return action.data.user_status || initialState;
Copy link
Member

Choose a reason for hiding this comment

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

Thanks for adjusting the type!

I'd still like to see this part too:

Ideally with a line of jsdoc saying what this commit message says: that older servers (older than what version?) don't send that property.

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 @borisyankov ! Several comments below.

I plan to merge some of these commits shortly; will comment separately when I do.

* * when missing the presence is not changed
* * can not be `false`
* @prop status_text - a string representing information the user decided to
* manually set as his 'current status'
Copy link
Member

Choose a reason for hiding this comment

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

Aha, I see the commit message for the later commit "Handle 'EVENT_USER_STATUS_UPDATE' action" has this information!

Currently the web app does update the `away` value separately from
the `status_text` string and that is the immediate plan for mobile
too. It is possible though that both values are changed at once.

 * if an event contains `away: false` this has the meaning of 'clear
   away overriding'
 * if an event contains `status_text: ''` this means 'clear status text'

There is value in keeping our internal state simpler and consistent
with what the back-end gives us as values. To achieve this we make
sure if we get `away: false` and `status_text: ''` we remove the
corresponding keys.

So it sounds like the answer is:

  • can't be undefined, nor ''
  • instead, will just be absent.

This is essential info for understanding the code around this type. Not only the reducer which maintains it, but also code which consumes it. And -- key test for what belongs in the code + comments, and not only commit message -- it's not at all specific to the change between this commit and its parent, and instead is 100% relevant to understanding the new code from scratch as it is, and for any future version of the codebase where these bits of code looks more or less like as they do here.

So let's stick that in the jsdoc too.

(There's also some information in there answering my questions about the semantics of the events. The same thing applies.)

@@ -54,6 +55,8 @@ export const getTopics = (state: GlobalState): TopicsState => state.topics;

export const getUserGroups = (state: GlobalState): UserGroup[] => state.userGroups;

export const getUserStatus = (state: GlobalState): UserStatusState => state.userStatus;
Copy link
Member

Choose a reason for hiding this comment

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

bump

return {
...state,
[action.user_id]: newUserStatus,
};
Copy link
Member

Choose a reason for hiding this comment

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

OK, now that I see the intended semantics for these (away has just two states, and status_text as empty-string is synonymous with missing), I think probably the cleanest thing would actually be to have a type like this in our Redux state tree: {| away: boolean, status_text: string |}. Then this code would look like

/** The status a user implicitly has when not present in the state. */
export const DEFAULT_STATUS = { away: false, status_text: '' };
// ...

const status = state[action.user_id] || DEFAULT_STATUS;
return {
  ...state,
  [action.user_id]: {
    away: action.away !== undefined ? action.away : status.away,
    status_text: action.status_text !== undefined ? action.status_text : status.status_text,
  },
};

One advantage of this would be that it makes explicit what it means when we don't have this kind of data on a given user -- unlike for many other kinds of data where it would mean "we don't know", here it means "they have these default values". The export is there for a selector to use to encapsulate that fact.

I'm OK merging a version with the existing design, though.

@gnprice
Copy link
Member

gnprice commented Feb 5, 2019

OK, and just merged 5 of these commits, as ac71a73..f4283ee!

I made just one edit to their code, along with small edits to the commit messages -- fixed one straggler mention of "user status" to mean a user's presence status:

diff --git src/account-info/AccountDetails.js src/account-info/AccountDetails.js
index f06dcdfb1..66833bb24 100644
--- src/account-info/AccountDetails.js
+++ src/account-info/AccountDetails.js
@@ -17,7 +17,7 @@ const componentStyles = StyleSheet.create({
   componentListItem: {
     alignItems: 'center',
   },
-  userStatusWrapper: {
+  statusWrapper: {
     justifyContent: 'center',
     flexDirection: 'row',
   },
@@ -49,7 +49,7 @@ export default class AccountDetails extends PureComponent<Props, void> {
           shape="square"
         />
         <ComponentList outerSpacing itemStyle={componentStyles.componentListItem}>
-          <View style={componentStyles.userStatusWrapper}>
+          <View style={componentStyles.statusWrapper}>
             <PresenceStatusIndicator presence={presence} hideIfOffline={false} />
             <RawLabel style={[styles.largerText, styles.halfMarginLeft]} text={user.email} />
           </View>

Found that with git grep -iE 'user.?status'.

@gnprice
Copy link
Member

gnprice commented Feb 5, 2019

(And for convenience, I just rebased the remaining 6 commits on top of those, and pushed that to the PR branch.)

@borisyankov
Copy link
Contributor Author

borisyankov commented Feb 5, 2019

Updated according to the latest feedback.

Since recently we are tracking all server events thus we are also
getting these just-added 'user_status' events.

* add a constant and an action type for this new event
* start issuing a Redux action from this event
* add Flow types for a new UserStatusState
* adds the base for a user status reducer that operates on a
  UserStatusState
* adds the object to the `GlobalState` type
* adds the state key to `discardKeys` and treat this data as
  temporary like the `presence` state
* adds a `getUserStatus` direct selector
Pass `user_status` to `fetch_event_types` to receive user status data
on initial load.  In the reducer, handle the resulting REALM_INIT data.

Also handle the usual actions that reset the state.
When a `EVENT_USER_STATUS_UPDATE` action is dispatched, handle it
by updating the relevant user key or add a new key.

Currently the web app does update the `away` value separately from
the `status_text` string and that is the immediate plan for mobile
too. It is possible though that both values are changed at once.

 * if an event contains `away: false` this has the meaning of 'clear
   away overriding'
 * if an event contains `status_text: ''` this means 'clear status text'

There is value in keeping our internal state simpler and consistent
with what the back-end gives us as values. To achieve this we make
sure if we get `away: false` and `status_text: ''` we remove the
corresponding keys.
@gnprice gnprice merged commit 5d95127 into zulip:master Feb 6, 2019
@gnprice
Copy link
Member

gnprice commented Feb 6, 2019

Merged -- thanks again @borisyankov !

I added under InitialDataUserStatus the comment discussed in a thread above.

I also squashed together the commit that asks the server to send that initial data, and the one that processes it in the reducer. Those feel to me like they each make more sense to understand in combination with the other, and the "request initial data" one especially is simple enough that the squashed one isn't so long as to get in the way of reading.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a-presence/status Presence, and "away status" P0 critical Highest priority
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants