-
-
Notifications
You must be signed in to change notification settings - Fork 666
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
mute: Request and consume new user_topic format in API; adapt our model #5382
Conversation
Following Greg's example of defining an enum in zulip#5382.
Following Greg's example of defining an enum in zulip#5382.
ae93f12
to
056ea85
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! This looks good; see a few comments below, including one where I'm not sure what the intended server API quite is.
I'll force-push a rebase, resolving the conflicts, just after posting this review. (I tried doing so before posting, but then went back because I'd written the comments on the old commits.)
I've also posted a review on the server PR, at zulip/zulip#21251 (review).
src/mute/muteModel.js
Outdated
// FlowIssue: sad that we end up having to write numeric literals here :-/ | ||
// But the most important thing to get from the type-checker here is | ||
// that the ensureUnreachable works -- that ensures that when we add a | ||
// new possible value, we'll add a case for it here. Couldn't find a | ||
// cleaner way to write this that still accomplished that. |
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.
Is this the same issue as #5384 (comment) ?
Should we leave a note about trying Flow enums with the flowlint rule require-explicit-enum-switch-cases
, new in Flow 153?
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.
Is this the same issue as #5384 (comment) ?
Ah, yep. I knew I'd run into that issue recently, went looking and found that other commit 04c5a03, but clearly didn't find the right search because this is what I was actually thinking of 🙂
Should we leave a note about trying Flow enums with the flowlint rule
require-explicit-enum-switch-cases
, new in Flow 153?
I think the main thing is just Flow enums themselves -- I believe that's already enough that Flow will point out an error if a switch isn't exhaustive.
Yep, here's a demo:
enum E { A, B };
(e: E) => {
switch (e) {
case E.A: break;
// case E.B: break;
// default: break;
}
}
That gives an error at the switch (e)
:
Incomplete exhaustive check: the member
B
of enumE
[1] has not been considered in check ofe
. [invalid-exhaustive-check]
If you uncomment either the other case, or the default, the error goes away because now it's exhaustive.
I believe what that flowlint does is just to say also you can't have a default case (without explicitly suppressing the lint rule): you have to name all the enum members, so that when someone adds another member to that enum they definitely get notified of each of the switches on it.
// new possible value, we'll add a case for it here. Couldn't find a | ||
// cleaner way to write this that still accomplished that. | ||
case 0: // UserTopicVisibilityPolicy.None | ||
return false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does UserTopicVisibilityPolicy.None
quite mean "the topic is not muted", i.e., isTopicMuted(…)
should be false?
From the doc in the current revision of zulip/zulip#21251:
0 = None. Used in events to indicate that this topic now follows its default user-topic configuration.
I don't know what that default is or where it's defined. Do you think the doc should do more to help me find that out?
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.
Yeah, probably that doc can be clearer.
It does indeed mean that the topic is not muted -- or rather that it's not individually muted. If the stream it's part of is muted, then the messages in the topic will get much the same behavior as if the topic were itself muted: they don't show up in unreads or in all-messages.
Rereading, I think the main issue is that "its default user-topic configuration" is not an accurate description. Rather, what that's trying to say is that the topic now follows the default visibility policy that it inherits from the stream. Which currently just means that it behaves as muted or unmuted according to how the user has the stream.
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.
src/utils/immutable.js
Outdated
@@ -0,0 +1,56 @@ | |||
/** General helpers to augment Immutable.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.
Just to point it out in case it's helpful (I don't have a strong preference): would you prefer src/immutableUtils.js
as a name for this? See discussion: #4683 (comment)
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.
Sure, thanks for the reminder of that thought.
src/api/initialDataTypes.js
Outdated
export type InitialDataUserTopic = {| | ||
/** When absent (on older servers), read `muted_topics` instead. */ | ||
// TODO(server-6.0): Introduced in FL 125 or so. | ||
+user_topics?: UserTopic[], |
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.
Could use $ReadOnlyArray
here, 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.
Hmm yeah, thanks.
Thanks for the review! Pushed a revision with the two small changes discussed above. |
Following Greg's example of defining an enum in zulip#5382.
Following Greg's example of defining an enum in zulip#5382.
And added unit tests. |
Also tested manually against the draft backend. Left a comment at zulip/zulip#21251 (review) , but all's well as far as the client code is concerned. |
(Just resolved rebase conflicts.) |
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! LGTM (with a few small things below); now I guess we wait till the server PR is merged.
src/api/eventTypes.js
Outdated
|
||
export type UserTopicEvent = {| | ||
...EventCommon, | ||
type: typeof EventTypes.user_topic, |
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.
nit: read-only
user_topics: [makeUserTopic(eg.stream, 'topic', UserTopicVisibilityPolicy.Muted)], | ||
}); | ||
const newState = tryGetActiveAccountState(fullReducer(eg.baseReduxState, action)); | ||
expect(newState).toBeTruthy(); |
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.
Can the mute-model code (what this file is responsible for testing) cause this newState
to be falsy? I wonder if this could just be an invariant
, as here: https://github.com/zulip/zulip-mobile/blob/41e50a740/src/__tests__/permissionSelectors-test.js#L41
Here, and in another case below.
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.
It could. I don't totally love this toBeTruthy
solution, because it doesn't narrow the type and so the next line still has to account for the falsy case:
expect(newState).toBeTruthy();
expect(newState && getMute(newState)).toEqual(makeMuteState([[eg.stream, 'topic']]));
But I don't love the invariant
solution either, here in a test. The Jest expect
API is nice because if the assertion is ever wrong, it gives good informative error output that's helpful for debugging. Also invariant
requires a message argument, which is a good thing for error cases in application code -- helpful for sorting out what's happening from a Sentry event or whatever, and for clarifying why the author thought it was an invariant -- but feels redundant here.
I'd probably go for invariant
if the narrowing is more necessary, like if the subsequent logic that relied on the condition was more complex so that repeatedly accounting for it was more of a pain. If so, I might go for something like invariant(newState, '');
, leaving the message empty.
I think zulip/zulip#21251 is waiting for your re-review: zulip/zulip#21251 (comment) |
ab0b2d9
to
1d461c7
Compare
The new server API having been test-deployed to CZO, I've just rebased, resolving several conflicts, and pushed to your PR branch; please take a look. 🙂 |
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.
While rebasing (see my comment above), I found a few comments to make; see below. And we'll still want to sync the feature-level numbers, etc., after the server work is merged.
@@ -734,9 +734,6 @@ export const action = Object.freeze({ | |||
// InitialDataMessage | |||
max_message_id: 100, | |||
|
|||
// InitialDataMutedTopics |
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.
wip mute: Request and consume new user_topic format in API
Commit-message nit:
In this commit, we start requesting the new `user_topic` format for
the data describing the "muted topics" feature.
On new servers, that causes the new format to appear, and the old to
disappear. Describe the API changes, and start consuming the new
format.
This seems to be true of the data as we receive it in the /register
response, but we can still get both the old and new shape of event. I don't yet understand why; that doesn't seem to match the doc test-deployed on CZO right now, does it:
Changes: Deprecated in Zulip 6.0 (feature level 134). Starting with this version, clients that explicitly requested the replacement
user_topic
event type when registering their event queue will not receive this legacy event type.
So, could just update with an explicit mention of the /register
response, if the API settles on the current behavior re: the events.
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 don't yet understand why
Oh, I think I see: event_types
is for explicitly requesting events, and fetch_event_types
is for explicitly requesting initial data. We do the initial-data part but leave event_types
unspecified, which the server takes as not "explicitly request[ing] the replacement user_topic
event type".
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.
Yeah, this is #5395.
export type InitialDataUserTopic = {| | ||
/** When absent (on older servers), read `muted_topics` instead. */ | ||
// TODO(server-6.0): Introduced in FL 125 or so. | ||
+user_topics?: $ReadOnlyArray<UserTopic>, |
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.
Here, and in UserTopicEvent
, I believe we're asking UserTopic
to depart slightly from its jsdoc—
/**
* A user's relationship to a topic; in particular, muting.
*
* Found in the initial data at `user_topics`, and in `user_topic` events:
* https://zulip.com/api/get-events#user_topic
*/
export type UserTopic = {|
+stream_id: number,
+topic_name: string,
+last_updated: number,
+visibility_policy: UserTopicVisibilityPolicyT,
|};
—to represent a change to a user's relationship to a topic.
Should we make a UserTopicUpdate
type, on the pattern of UserStatusUpdate
, for use here and in the event?
It seems that the server data we're modeling with InitialDataUserTopic['user_topics']
is a change from the UserTopic
"zero value," which has visibility_policy: UserTopicVisibilityPolicy.None
. The API doc doesn't make this change-from-zero semantics explicit, but a clue is that it only lists one of the two values for visibility_policy
: the non-"zero" value. From the doc as test-deployed on CZO now (formatting/links lost):
visibility_policy: integer
An integer indicating the user's visibility configuration for the topic.
1 = Muted. Used to record muted topics.
So that could be good to say in the jsdoc.
Currently, the substance of what we'd want as a UserTopicUpdate
type would coincide with the current UserTopic
type, I think. But if the server wants to send richer data, then they might stop coinciding. E.g., a "user–topic relationship" gains a property "foo" besides "visibility_policy", and the "change to user–topic relationship" payload starts dropping "foo" or "visibility_policy" if the other property changed but it didn't. We see this happening with user statuses.
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.
Here, and in
UserTopicEvent
, I believe we're askingUserTopic
to depart slightly from its jsdoc […] to represent a change to a user's relationship to a topic.
Here in the initial data, I don't see it as representing a change in anything: it's telling us what the relationship is now, as of the snapshot represented by this /register response.
In the user_topic
event, it is representing a change, but the way it does so is by providing the new state of that relationship.
It seems that the server data we're modeling with
InitialDataUserTopic['user_topics']
is a change from theUserTopic
"zero value," which hasvisibility_policy: UserTopicVisibilityPolicy.None
. The API doc doesn't make this change-from-zero semantics explicit, but a clue is that it only lists one of the two values forvisibility_policy
: the non-"zero" value.
I see. Yeah, I still don't think of this as a change, because there's no particular previous period when the value was something else. The value we're being told is the value that it has now, and that it's had since, as far as we know, time immemorial.
But it's true that it could be thought of as a diff, the result of comparing two values. Still, for each individual user-topic relationship, the diff is represented as simply the entire actual state, if it's present at all. So I think this jsdoc is still accurate, and a good succinct description of what a UserTopic
should be thought of as.
I'll add a few words at the two places it's used in the API types, to clarify how those respectively use the UserTopic
type.
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.
Currently, the substance of what we'd want as a
UserTopicUpdate
type would coincide with the currentUserTopic
type, I think. But if the server wants to send richer data, then they might stop coinciding. E.g., a "user–topic relationship" gains a property "foo" besides "visibility_policy", and the "change to user–topic relationship" payload starts dropping "foo" or "visibility_policy" if the other property changed but it didn't. We see this happening with user statuses.
True. I think we can cross that bridge if/when we come to it. I don't think there are currently any planned features that would add something to a user-topic value other than the visibility policy.
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.
But it's true that it could be thought of as a diff, the result of comparing two values.
Ah, yeah, I think my message would have been less confusing if I'd used the word "diff" instead of "change". 🙂
787cbab
to
e118f33
Compare
New revision pushed; now 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! LGTM with one tiny comment below; please merge at will.
src/mute/muteModelTypes.js
Outdated
/** | ||
* The "visibility policy" our user has chosen for each topic. | ||
* | ||
* See jsdoc of UserTopicVisibilityPolicyT and related items for background. |
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.
nit: UserTopicVisibilityPolicy
(without the T
, happily, thanks to Flow enums!)
This just adds type definitions, without changing the types we expect from the server. After this, as the next steps: * we'll migrate the data structure we maintain in our own store to one that can smoothly handle the new API format; * then as a separate step after that, we'll switch to actually consuming the new API format. It's convenient to use this UserTopicVisibilityPolicy type in describing that new data structure's type. Hence we add it early, in this commit, before we actually start requesting the new format (or update our event and initial-data types to reflect doing so).
Ideally we'd rename this to something like `state.userTopic` too. But it's currently a pain to actually successfully rename a state subtree (and not have its ghost return at the old name): zulip#5381. So just keep calling it "mute" for now. Also add tests for the model. Note that we don't have to update any of the existing tests! They use `makeMuteState`, which uses the reducer; so they get the new data structure automatically.
This is a better pattern for reducing import cycles. The explicitness also seems helpful to the reader, though there isn't a big difference.
We'll reuse some of these in other models as we use Immutable more.
In this commit, we start requesting the new `user_topic` format for the data describing the "muted topics" feature. On new servers, that causes the new format to appear, and the old to disappear. Describe the API changes, and start consuming the new format. This should have no user-visible effect -- currently the new format describes exactly the same information as the old. Taking the client plus (a properly behaving) server as one system, this change is therefore NFC. We don't call the change NFC, though, because it does change the client's behavior as witnessed by the server. (This change does switch from stream names to stream IDs in the API... but only for data that we get from the server through the event system, not for data we send or receive in any request outside that system. That means that (unless affected by some server bug) it's always working with a stream name-to-ID mapping that exactly matches what the server had at the time it sent that data. So even if a stream gets renamed concurrently with getting some data here, there's no race and no bug. OTOH when we *change* this at the server on the user's behalf, that does suffer from such a race. But that part of the API was fixed long ago to accept stream IDs; we already have a TODO-server comment, at `api.setTopicMute`, to start relying on that update.) Fixes: zulip#5380
e118f33
to
deab4ad
Compare
Thanks for the review! Merging with that fix. |
This implements for mobile the client side of zulip/zulip#21251 / zulip/zulip#21015.
This is a draft PR for two reasons:
I should write some unit tests, and also test these changes manually against the draft backend.But I believe the code is good, and ready for review. The "wip" markers on some commits correspond to TODO items in the commit messages, basically for the two bullet points mentioned above.
Fixes: #5380