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

ActionSheets: Refactor to tighten up types #4541

Merged
merged 11 commits into from
Mar 26, 2021

Conversation

WesleyAC
Copy link
Contributor

This will allow us to use the shared messageActionSheet code in the
React Native parts of the codebase, rather than only the webview parts
of the codebase. It's also generally a cleaner and more type-safe
approach.

The test coverage for this seems not great, but I manually tested
clicking on most of the buttons on both styles of action sheet in the
app.

@WesleyAC WesleyAC requested review from gnprice and chrisbobbe March 17, 2021 12:12
@WesleyAC WesleyAC force-pushed the refactor-message-action-sheet branch 3 times, most recently from 0b63cc4 to 15edce8 Compare March 17, 2021 12:41
@WesleyAC
Copy link
Contributor Author

There's still some stuff that's not great about this: particularly, every ButtonDescription requires every possible callback, so we need to, for instance, pass in startEditMessage on every button, even if none of the buttons are going to edit the message. This isn't a new problem though, just something we might want to tighten up in the future. In the meantime, I've sort of hacked my way around it just by passing in (_) => {} when it's unused, which is OK but not great.

Not sure what the best way to do something like this in flow is. Maybe each button should have it's own type, and we should have a buttonData: MessageButtonData | EditButtonData | (etc...) on the ButtonDescription? But that might be pretty verbose, I'm not sure.

@WesleyAC
Copy link
Contributor Author

The current state of where the ButtonDescription options are used is as follows:

Required by both message and header actionsheets: auth, message, dispatch, _
Only used in message actionsheets: ownUser, startEditMessage
Only used in header actionsheets: subscriptions

However, we probably want to change header actionsheets to stop using message, and use new stream and topic options instead, since it doesn't require anything else, and that's cleaner on the whole.

@WesleyAC
Copy link
Contributor Author

And just to be abundantly clear on where this stands: I think merging this PR as-is is an improvement (in that it's a step towards #4541), although this refactor is far from complete (I still need to make the changes I mentioned above). I'm going to keep working on this refactor, but I think it makes sense to review and merge this now just so the refactor doesn't turn into an enormous PR.

@gnprice
Copy link
Member

gnprice commented Mar 18, 2021

Thanks @WesleyAC !

I like splitting showActionSheet into a showHeaderActionSheet and showMessageActionSheet. They naturally have somewhat different types, so splitting them makes it possible to start expressing that clearly. That split, as a commit of its own, would be a good first commit in this refactor.

I'm not such a fan of turning these signatures into having long lists of positional arguments (here 8 of them) -- I think well before that point it typically becomes better to use objects to effectively get named parameters, to avoid having to match up a long somewhat arbitrary sequence between definition and call site. That's part of what backgroundData is doing here. So all these arguments:

      backgroundData.auth,
      backgroundData.mute,
      backgroundData.subscriptions,
      backgroundData.ownUser,
      backgroundData.flags,

would be cleaner as an object, or as part of an object. Perhaps the simplest way to do this would be to pass literally backgroundData itself, but loosen the type required for it from BackgroundData to an inexact object type with just the properties needed, like { auth: Auth, /* … */, flags: FlagsState, ... }. That way the function's signature is expressing that it needs these properties and doesn't care about any others -- and you could add another call site that passes the needed properties without bothering about the other parts of BackgroundData that aren't relevant here.

Zooming out a bit, one thing you'll see we like to do in a refactor like this is to split it into a series of relatively small commits, each self-contained but only expressing a single idea. For some recent examples, take a look at #4437, #4424, or #4442. Here that might look something like:

  • Split the function into two; not yet changing the signature at all except there's now two of them, and that first boolean argument is gone.
  • Factor out makeButtonCallback.
  • Loosen the types of the backgroundData parameters on the various constructFooButtons helpers and also the showFooActionSheet functions.
    • Or, as an example of how I'd make the exact set of changes as this version of the PR but just split them up: one commit that changes the construct* signatures the way this PR does, and then another that changes the show* signatures.
  • Drop the narrow argument on showHeaderActionSheet.

I find that strategy helps a lot in reading and understanding and reviewing the changes. It also helps in making it possible to develop larger refactors and manage the complexity of them.

(BTW chat thread here: https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/shared.20actionsheets.20code/near/1134303 for cross-reference when someone else, or our future selves after a few days from now, is reading this.)

@gnprice
Copy link
Member

gnprice commented Mar 18, 2021

However, we probably want to change header actionsheets to stop using message, and use new stream and topic options instead, since it doesn't require anything else, and that's cleaner on the whole.

Definitely agreed it'd be good to stop passing it a message, which is unduly specific. It can't be just stream and topic, I think, because you can also get one of these for a PM conversation if you're looking at an interleaved view that contains PMs: all-messages, @-mentions, or search results. Probably the right type for it is Narrow, defined in src/utils/narrow.js.

(And then we should do something in the names to make it clear that this is the narrow the header is about, not the narrow of the surrounding view -- whereas the narrow: Narrow that the message case uses is the latter.)

@WesleyAC WesleyAC force-pushed the refactor-message-action-sheet branch from 15edce8 to e9f9f7d Compare March 18, 2021 08:12
@WesleyAC
Copy link
Contributor Author

@gnprice ok, I've split it up into multiple commits and changed most of the functions to take objects as args instead of positional args, should be ready for a review.

I'm not sure what the preferred way of writing a function that takes only an object argument is — I've opted to use destructuring, and duplicate the names of the args in the function signature, but passing a named args parameter would also work. Is there a way to do the destructuring without the verbosity of repeating the argument names?

@WesleyAC
Copy link
Contributor Author

Perhaps the simplest way to do this would be to pass literally backgroundData itself, but loosen the type required for it from BackgroundData to an inexact object type with just the properties needed, like { auth: Auth, /* … */, flags: FlagsState, ... }. That way the function's signature is expressing that it needs these properties and doesn't care about any others -- and you could add another call site that passes the needed properties without bothering about the other parts of BackgroundData that aren't relevant here.

Oh, hmm, I didn't do it this way, but that does seem cleaner — I'll add another commit to change it to work like this.

@WesleyAC
Copy link
Contributor Author

Ok, now it should be all good :)

@WesleyAC
Copy link
Contributor Author

Just manually tested the new commits, everything appears to work as expected.

@WesleyAC
Copy link
Contributor Author

Since this is split into separate commits, I'm just keeping on pushing refactoring commits here, let me know if that's not the correct thing to do. I figure we can merge them a few at a time, if we need/want to.

@WesleyAC WesleyAC force-pushed the refactor-message-action-sheet branch 4 times, most recently from 5557600 to a7d152e Compare March 18, 2021 14:47
@WesleyAC WesleyAC changed the title messageActionSheet: Refactor to not rely on BackgroundData ActionSheets: Refactor to tighten up types Mar 18, 2021
@WesleyAC
Copy link
Contributor Author

Ok, I think I'm done for now — this is a pretty large stack of commits, but each individual one should be pretty easy to review, I think. Let me know if I should bias more towards waiting for feedback before making a big commit stack in the future.

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! I appreciate how nicely split out these changes are.

I've read through to about here:
c261b26 ActionSheets: Split showActionSheet into multiple functions
489b6d2 ActionSheets: Factor out makeButtonCallback function
a6956b0 ActionSheets: Refactor construct*ActionButtons to avoid BackgroundData
0096646 ActionSheets: Replace ConstructSheetParams with more specific types
664626c ActionSheets: Remove ButtonCode, pass around objects instead
99efbd4 ActionSheets: Change ButtonDescription type to be more specific
963c978 ActionSheets: Have constructHeaderActionButtons stop taking message arg

and am about to go make and eat dinner, and I figure you may be online before I'm back at my desk. So I wanted to send you the comments I have so far. The remaining commits I haven't looked closely at are:
3f443c6 ActionSheets: Get rid of makeButtonCallback function
63d03d5 ActionSheets: Change HeaderButton callback to take stream and topic
769d85b ActionSheets: Remove message arg from showHeaderActionSheet
a7d152e ActionSheets: Remove startEditMessage from showHeaderActionSheet

Let me know if I should bias more towards waiting for feedback before making a big commit stack in the future.

This is about right for the scope to send as one PR (when the overall scope of a project is this or more, that is -- smaller PRs are of course great when the work can be done in something smaller 🙂 ).

I think the key thing to aim for is to try to post as a PR, or else post something on Zulip about what you've done / what's hard / what you've learned / etc., at least every day that you're working on a given thing. So if it's getting toward the end of your day and there's something you spent time on that day and haven't posted, then try to take some initial segment of those commits and polish them up to send as a PR.

src/message/messageActionSheet.js Show resolved Hide resolved
src/message/messageActionSheet.js Show resolved Hide resolved
src/message/messageActionSheet.js Show resolved Hide resolved
src/message/messageActionSheet.js Outdated Show resolved Hide resolved
Comment on lines 403 to 409
try {
await pressedButton({
subscriptions: params.backgroundData.subscriptions,
auth: params.backgroundData.auth,
ownUser: params.backgroundData.ownUser,
...params,
...callbacks,
});
Copy link
Member

Choose a reason for hiding this comment

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

Seeing this duplication, I realize that it would have been a bit cleaner to have factored this piece out first and then split this function into two versions, rather than vice versa as I suggested above. 🙂 This is fine, though -- it's not a huge amount of duplication, and it goes away in the next commit.

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 ended up actually keeping the duplication, since that turned out much easier than making the types work out once we had more than one type of button — that's worth the pretty small amount of duplication, IMO (although if there's a nice flow way to write it, I'm happy to see it)

Copy link
Member

Choose a reason for hiding this comment

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

Sent #4566 with a way to do this. I agree the duplication isn't a big problem because the code is so small. But the Alert.alert bit in particular feels like the sort of thing that can easily get annoyingly out of sync when duplicated, so I think there's some value in keeping it deduplicated.

src/message/messageActionSheet.js Outdated Show resolved Hide resolved
src/message/messageActionSheet.js Outdated Show resolved Hide resolved
src/webview/handleOutboundEvents.js Outdated Show resolved Hide resolved
src/webview/handleOutboundEvents.js Show resolved Hide resolved
@WesleyAC WesleyAC force-pushed the refactor-message-action-sheet branch 5 times, most recently from 6124ae2 to 72ae42c Compare March 19, 2021 15:04
@WesleyAC
Copy link
Contributor Author

I've tested that the final commit here works on my android device, and git rebase origin/master --exec 'tools/test' reports that tests pass at every commit. Should be ready for another review.

@WesleyAC WesleyAC force-pushed the refactor-message-action-sheet branch from 72ae42c to 082b7f7 Compare March 19, 2021 16:53
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 revisions! This all looks great, modulo the nit below. I'm going to go ahead and merge with just a small commit on top fixing that.

I also see your question at #4541 (comment) above, and after merging I'll take a few minutes to see what I can come up with.

Comment on lines 300 to 267
flags: FlagsState,
}>,
Copy link
Member

Choose a reason for hiding this comment

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

nit: make explicitly inexact (here and a few others below)

WesleyAC and others added 11 commits March 26, 2021 15:37
This splits showActionSheet into a showHeaderActionSheet and a
showMessageActionSheet function, since the two of them conceptually
require different arguments and do different things.

Right now, they take the same set of arguments, but this commit sets us
up to change that in the future, and tighten up the typing more.
This changes the:

* constructHeaderActionButtons
* constructNonHeaderActionButtons
* constructMessageActionButtons
* constructOutboxActionButtons
* showHeaderActionSheet
* showMessageActionSheet

functions in a few ways:

* They no longer explicitly take a `BackgroundData` object, but instead
  take an inexact object with just the parameters that each function
  uses.
* The show*ActionSheet functions have been converted to use named
  arguments via an object.
This removes the ButtonCode infrastructure, and instead has the code for
constructing button lists pass around ButtonDescription objects.

This will enable us to split `ButtonDescription` into a `HeaderButton`
and `MessageButton` type (or a `Button<Message | Header>` type, more
likely), and have each of those take only the arguments it needs - for
instance, we can avoid passing `startEditMessage` callback to a
`Button<Header>`, since we know that'll never be used.

This also lets us avoid keeping around a global list of all possible
buttons, while still being able to share button code across multiple
actionsheets in a typesafe way (for instance, the cancel button).

The main downside is that our tests now rely on the `title` of the
button to check if the correct buttons have been inserted, since we no
longer have a `ButtonCode` to compare. At first glance, this seems bad,
but I think it's actually OK:

* We apply i18n in the rendering step, so we don't need to worry about
  the strings being translated - there are no calls to `_` in the
  codepath that the tests are checking.
* We're not really much worse off than we were before - previously,
  changing the name of a button in the code would necessitate changing
  the tests, now changing the title will require that. While a title
  change is probably more common, I don't think we're at any increased
  risk of "stringly typed" typo bugs - if we change a title, the tests
  will start failing and we'll need to go through and patch it up, but
  none of the non-test logic relies on the button titles.
This commit replaces the `ButtonDescription` type with a `Button<Args>`
type, which will allow us to in the future just pass in the required
arguments for each type of button (so that, for instance, a
`Button<HeaderArgs>` doesn't need to require a `startEditMessage`
callback).
Previously, long-pressing a PM header in the "All Messages" (or similar)
view would pop up a actionsheet, which only showed the name of the PM
and a "Cancel" button. This was pretty silly, and makes the ActionSheet
code more complicated than it needs to be, so we'll just pop up a toast
showing the name of the PM instead. This is useful in the case of a PM
where the title is long enough that it's truncated in the UI.

The code for the toast text is taken from `getActionSheetTitle` in
src/message/messageActionSheet.js - it's duplicated in this commit for
type safety reasons, but the duplication will be removed in the next
commit.
This changes the constructHeaderActionButtons function to take a stream
and topic, instead of a message, since that is conceptually closer to
what it's doing.

I haven't routed this through to showHeaderActionButton yet, since all
of the button callbacks currently still require a Message, but that's
the goal in the future.
…opic

This changes the Button<HeaderArgs> callback to take a stream and topic,
rather than a message, since the only thing the callbacks need is the
stream and topic.
showHeaderActionSheet now takes a stream and topic, rather than a
message, since we only used the stream and topic anyways.
It's intentional that these "background data" types are inexact
object types: they're meant to be passed values that may have a
larger menu of different pieces of background data (in particular
values of the BackgroundData type defined in MessageList.js), and
these types just specify the particular pieces of data that the particular function requires.
@gnprice gnprice force-pushed the refactor-message-action-sheet branch from 1f0427b to 96ef22d Compare March 26, 2021 22:42
@gnprice gnprice merged commit 96ef22d into zulip:master Mar 26, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants