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

action sheet: add and implement delete topic option #3824

Merged
merged 6 commits into from
Feb 29, 2020

Conversation

agrawal-d
Copy link
Member

@agrawal-d agrawal-d commented Jan 18, 2020

Closes #3806

delete-topic

image

@agrawal-d
Copy link
Member Author

agrawal-d commented Jan 18, 2020

Flow issue I cant figure out : In line messageActionSheet.js:88, flow assumes message is of type Outbox even though the definition is message: Message | Outbox. This causes a flow error.

Is this fixable, or is thus a flow bug?

@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.

Flow issue I cant figure out : In line messageActionSheet.js:88, flow assumes message is of type Outbox even though the definition is message: Message | Outbox. This causes a flow error.

Flow isn't assuming that message is of type Outbox; rather, you're assuming that message is of type Message. Flow is complaining (correctly, I believe) that you're trying to access a value that doesn't exist on Outbox.

The fix for this is going to be somewhat more complicated than mere syntax. (For example, it will have to correctly handle the case where the message whose header was selected was an Outbox, but has been successfully delivered while the user is still looking at the confirmation dialog.)

Comment on lines 87 to 116
{
text: 'Delete topic',
onPress: () => api.deleteTopic(auth, message.stream_id, message.subject),
},
Copy link
Contributor

Choose a reason for hiding this comment

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

This option needs to be marked as style: 'destructive' for iOS.

Also, it needs to report failure somehow (if #3795 is merged before this).

@@ -79,6 +79,26 @@ const muteTopic = ({ auth, message }) => {
};
muteTopic.title = 'Mute topic';

const deleteTopic = ({ auth, message }) => {
Alert.alert(
`Are you sure you want to delete the topic '${message.subject}' ?`,
Copy link
Contributor

Choose a reason for hiding this comment

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

This construction, and therefore this wording, won't work as is: it's impossible to localize properly.

(Since the topic is a fixed string that shouldn't be inflected in any language, we might be able to do something like

  const alertTitle = _('Deleting topic: $topic').replaceAll('$topic', message.subject);

but that implies a can of worms I'm not willing to casually open. @gnprice?)

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'll wait for @gnprice 's response, before pushing any changes.

Copy link
Contributor

Choose a reason for hiding this comment

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

Erratum: this JS construct can't be localized, but the i18n library we have can help.

  const alertTitle = _.intl.formatMessage(
    { id: "Are you sure you want to delete the topic '{topic}'?" },
    { topic: message.subject },
  );

Copy link
Member Author

Choose a reason for hiding this comment

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

The value of id is being taken literally, without substitution for topic. Using defaultMessage appears to work.

I referred to https://github.com/formatjs/react-intl/blob/master/docs/API.md#formatmessage

@@ -50,6 +50,7 @@
"Copy to clipboard": "Copy to clipboard",
"Link copied to clipboard": "Link copied to clipboard",
"Mute topic": "Mute topic",
"Delete topic":"Delete topic",
Copy link
Contributor

Choose a reason for hiding this comment

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

Space after the colon, for consistency.

@agrawal-d
Copy link
Member Author

@ray-kraesig I'm not sure how to proceed with this, with respect to the flow issue and handling the corner cases you mentioned #3824 (review).
Can you please tell me in what I should do next to resolve that? Thanks!

@rk-for-zulip
Copy link
Contributor

It's not really a Flow issue; it's a functional issue that Flow is recognizing. message may actually be an Outbox; that case will need to be tested for and handled separately, much like the delete-message callback does.

However, the delete-message action isn't a great template here, as it has the bug I mentioned: if a user long-presses a message before it's sent, then the delete-message callback will still attempt to delete it from the local Outbox (from which it's already been removed), rather than from the server, and no error will be given.

Resolving that (for either delete-message or delete-topic) will require some thought and some testing.

@agrawal-d agrawal-d removed the request for review from gnprice February 3, 2020 06:29
@agrawal-d agrawal-d changed the title action sheet: add and implement delete topic option [WIP] action sheet: add and implement delete topic option Feb 6, 2020
dispatch: Dispatch,
getState: GetState,
) => {
const messages: $ReadOnlyArray<Message | Outbox> = getMessagesForNarrow(getState(), narrow);
Copy link
Member Author

@agrawal-d agrawal-d Feb 6, 2020

Choose a reason for hiding this comment

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

@ray-kraesig can you please help me out here? getMessagesForNarrow() is returning an empty array here, even though the narrow does contain several messages.
Output of the console statement ( line 44 ):
image

Am I using the selector incorrectly?

Copy link
Member Author

@agrawal-d agrawal-d Feb 8, 2020

Choose a reason for hiding this comment

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

@agrawal-d agrawal-d changed the title [WIP] action sheet: add and implement delete topic option action sheet: add and implement delete topic option Feb 8, 2020
@agrawal-d agrawal-d force-pushed the delete-topic branch 2 times, most recently from b366b10 to 620a85c Compare February 8, 2020 18:18
@agrawal-d
Copy link
Member Author

@ray-kraesig have pushed some changes.

@agrawal-d
Copy link
Member Author

Tests are failing, I will update them if the current change is acceptable.

@andreasoc
Copy link

sigh

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.

api.deleteTopic and deleteMessagesForTopic are self-contained enough that they could reasonably be moved to their own commit(s), to keep the size of this one down.

Please ensure tests pass before resubmitting for review.

);
Alert.alert(
alertTitle,
'This will also delete all messages in the topic.',
Copy link
Contributor

Choose a reason for hiding this comment

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

This string also needs localization support.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

Comment on lines 96 to 108
{
id: 'delete_topic_warning',
defaultMessage: "Are you sure you want to delete the topic '{topic}' ?",
},
Copy link
Contributor

Choose a reason for hiding this comment

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

  • defaultMessage and id should be identical.
  • id should be added to the localization database.
  • There should be no space before the question mark.

Formally, id is the localization database key, while defaultMessage is the message displayed if no message with key id is found in the localization database for the current language.

However, we require that defaultMessage be identical to the en database's entry for id (and, implicitly, that such an entry must exist). To ease the translation process, it's also our policy that id should be identical to these wherever feasible.

Copy link
Member Author

Choose a reason for hiding this comment

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

Changed.

'This will also delete all messages in the topic.',
[
{
text: 'Delete topic',
Copy link
Contributor

Choose a reason for hiding this comment

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

This string also needs localization support.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

onPress: async () => {
dispatch(deleteMessagesForTopic(getNarrowFromMessage(message, ownEmail), message)).catch(
err => {
Alert.alert(`${_(deleteTopic.errorMessage)} '${message.subject}'`, err.message);
Copy link
Contributor

Choose a reason for hiding this comment

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

Concatenating localized strings is essentially never safe. (Consider Japanese, where this would need to be something like 「{topic}」というトピックを削除できません.)

This would need to be a separately localized string... except that there shouldn't be any need for this interior Alert at all, because failure here should be propagated up to and handled by the new error-reporting mechanism in showActionSheet.

In order to do that, you'll effectively have to await on the Alert.alert – which isn't awaitable on its own, so you'll need to promisify it appropriately. See, e.g., here for a partial example.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed.

style: 'destructive',
},
{
text: 'Cancel',
Copy link
Contributor

Choose a reason for hiding this comment

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

This string also needs localization support.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

@agrawal-d
Copy link
Member Author

agrawal-d commented Feb 13, 2020

@ray-kraesig I've pushed some changes. I've also created a new test for the 'Delete topic' button.

@agrawal-d
Copy link
Member Author

Please ensure tests pass before resubmitting for review.

Should tests always pass before requesting a review?
Is it ever okay if tests are not passing when requesting an initial review? I'm asking because the initial review may result in significant changes requiring the tests to be changed again.

@rk-for-zulip
Copy link
Contributor

Should tests always pass before requesting a review?

Almost always, yes. (Although see below.)

Is it ever okay if tests are not passing when requesting an initial review? I'm asking because the initial review may result in significant changes requiring the tests to be changed again.

That is sometimes the case! However, when it is, the review request should always be accompanied with an explanation of why the tests don't pass, and what questions you need to have answered in order to fix them. Otherwise, I'm probably going to infer that you're still working on the PR, and that it's not actually ready for review.

Putting off fixing the tests until you get a first review may be appropriate when those fixes would be a significant and potentially wasteful undertaking in themselves. (Although one would definitely still need a comment stating and explaining this.) However, in this case:

  • Fixing the tests is straightforward – not quite as straightforward as it should be (cleanup is badly needed!), but still, not terribly intrusive.
  • Essentially any implementation of a Delete topic button-insertion test is going to need to know whether the user is an admin. Even a total rewrite would be very unlikely to change that.

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.

If we're going around again, one minor fix to the code itself:

[
{
text: _('Delete topic'),
onPress: async () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

This shouldn't need to be async. (Compare with the other branch.)

@agrawal-d
Copy link
Member Author

agrawal-d commented Feb 25, 2020

@gnprice I'm having some trouble converting messageActionSheet-test.js to a type checked one.

  1. I added // @flow strict-local to the top.
  2. This causes the following error in several places:
For:
const store = mockStore({ ...

Cannot call `mockStore` with object literal bound to `middlewares` because  object literal [1] is incompatible with  array type [2].Flow(InferError)
messageActions-test.js(14, 31): [1] object literal
redux-mock-store_v1.2.x.js(26, 42): [2] array type

I think flow is not not using our mockStore defined in redux-mock-store.js and instead usinng redux-mock-store_v1.2.x.js to infer the type - moving the contents of redux-mock-store.js into messageActions-test.js somehow fixes this error.

  1. After I temporarily fixed the error in step 2, I get several more errors of these two types:
For :
store.dispatch(doNarrow(streamNarrowObj));

Cannot call `store.dispatch` with `doNarrow(...)` bound to `action` because property `type` is missing in  function [1] but exists in  object type [2].Flow(InferError)
messagesActions.js(24, 83): [1] function
redux-mock-store_v1.2.x.js(14, 28): [2] object type
For :
expect(actions[0].type).toBe('DO_NARROW');

Cannot get `actions[0].type` because property `type` is missing in  function [1].Flow(InferError)

I tried a lot, but couldn't think of how to resolve these :'(

@agrawal-d
Copy link
Member Author

@ray-kraesig I've removed the async keyword in the latest force-push.

@rk-for-zulip
Copy link
Contributor

@gnprice I'm having some trouble converting messageActionSheet-test.js to a type checked one.

The lines of code quoted here appear to be from messageActions-test.js, not messageActionSheet-test.js. That file's a little out of scope for this PR, I think. 🙂

@agrawal-d
Copy link
Member Author

agrawal-d commented Feb 26, 2020

The lines of code quoted here appear to be from messageActions-test.js, not messageActionSheet-test.js. That file's a little out of scope for this PR, I think. slightly_smiling_face

Oh, this is hilarious! Thanks!

I spent such a long time on trying to fix that file :'(

@agrawal-d agrawal-d force-pushed the delete-topic branch 3 times, most recently from d19a024 to 0bf5b44 Compare February 26, 2020 21:39
@agrawal-d
Copy link
Member Author

@gnprice @ray-kraesig I've made all the changes - the tests are type-checked now. Can you please review this? Thanks!

@agrawal-d
Copy link
Member Author

Not sure why the ordering of commits is different in GitHub UI, here's the output of
git log --pretty=oneline --abbrev-commit

0bf5b440 (HEAD -> delete-topic, agrawal-d/delete-topic) action sheet: Add delete topic feature.
4227e799 topic actions: Create action 'deleteMessagesForTopic'.
fc435354 api: Create API function for 'delete topic'.
acf3fb75 messageActionSheet [nfc]: Refactor tests to make them type-checked.
51be05fe exampleData: Add sample data of types 'BackgroundData' and 'Narrow'.

@chrisbobbe
Copy link
Contributor

chrisbobbe commented Feb 26, 2020

Not sure why the ordering of commits is different in GitHub UI

Well spotted! 🙂 Greg explained this to me here; it's a bug in GitHub where they're sorting by author date instead of committer date. I think we have this documented somewhere, but I haven't found it in a quick search on https://zulip.readthedocs.io/en/latest/overview/index.html, where most of our git-related documentation lives.

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 nitpicks about data location. (Also, there are merge conflicts, so a rebase is needed anyway.)

@@ -137,6 +138,13 @@ const displayRecipientFromUser = (user: User): PmRecipientUser => {
});
};

export const narrow: Narrow = [
Copy link
Contributor

Choose a reason for hiding this comment

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

There's probably no benefit in having a Narrow here; pretty much every test that needs a Narrow needs to specify what sort of Narrow it's using.

In particular, this is true of your tests in mAS-test.js, which actually assume that they're getting a Narrow with this structure and these values: if this were replaced with, e.g. [{operator: 'is', operand: 'starred'}], those tests would break. That makes this not "example data" – it's test-specific data, and should go alongside the tests it's a part of.

Copy link
Member Author

Choose a reason for hiding this comment

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

You're right - thanks for the explanation!

historical: {},
is_me_message: {},
},
mute: [['announce', 'stream events']],
Copy link
Contributor

Choose a reason for hiding this comment

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

Also test-specific data. (Probably belongs in baseBackgroundData.)

Comment on lines 24 to 28
...eg.streamMessage(),
id: 3,
reactions: [],
avatar_url: '',
client: '',
Copy link
Contributor

Choose a reason for hiding this comment

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

Since you're using eg.streamMessage() now, you can just do const message = eg.streamMessage({ id: 3 }) here (and similarly elsewhere) – there's no need to explicitly specify any properties which aren't relevant to the test.

},
mute: [['announce', 'stream events']],
ownUser: selfUser,
subscriptions: [
Copy link
Contributor

Choose a reason for hiding this comment

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

Idem.

@agrawal-d
Copy link
Member Author

@ray-kraesig thank you for the review. I have made the changes you requested. Please review it again.

@agrawal-d
Copy link
Member Author

Greg explained this to me here; it's a bug in GitHub where they're sorting by author date instead of committer date.

Oh - that's interesting. Thank's @chrisbobbe !

We are moving towards making all our tests type-checked. This
commit is a move in that direction.
The 'delete topic' endpoint allows for the deletion of all messages
in a stream for a given topic.
'deleteMessagesForTopic' deletes all messages (including those in
outbox) for a given topic in a stream.
The webapp offers the feature to delete all messages within a
topic for a stream. This feature is missing in the mobile app.

Add 'Delete topic' button in message action sheet.

Fixes zulip#3806.
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.

To avoid another round-trip (over the weekend, at that) with only trivial changes, I've appended a couple of commits to this PR. The first addresses the issue below; the second was a suggestion from @gnprice when reviewing the former.

@@ -34,3 +36,25 @@ export const fetchTopicsForActiveStream = (narrow: Narrow) => async (
}
dispatch(fetchTopics(stream.stream_id));
};

export const deleteMessagesForTopic = (narrow: Narrow, message: Message | Outbox) => async (
Copy link
Contributor

Choose a reason for hiding this comment

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

I should have noticed this during one of the preceding passes, but – why does this function take a Message | Outbox? All the information it needs is in the Narrow.

(For that matter, it would be fairly simple just to pass the stream name and topic as parameters – there's no need to wrap them up in a Narrow object which you then have to check, or assume, to be of the right shape.

Narrow should be a type we'd want here. Unfortunately it's really a server API type that we've pressed into service for insufficiently-closely-related purposes in the mobile app. 😞 We've been discussing its replacement for some time.)

`deleteMessagesForTopic` currently takes both a `Narrow` and a
`Message | Outbox`. However, this is redundant; all the relevant
information can be obtained from either one.

Furthermore, neither is really necessary: we can just ask the caller
to extract the data and hand it to us. Since the caller already knows
that it's operating on a `stream` message, it's actually less trouble
than constructing the `Narrow`.
Outbox messages of type `private` will not have a comparable
`.display_recipient`, and will have no `.subject` at all.

While the comparisons currently performed are harmless and will, in
this case, yield the right result, it's cleaner conceptually (and
slightly safer when refactoring) to actually check the type first.
@rk-for-zulip
Copy link
Contributor

And merged. Thanks!

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.

Feature request: delete topics
5 participants