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

slash commands: Create /mute_topic #9754

Closed
wants to merge 2 commits into from

Conversation

rheaparekh
Copy link
Collaborator

@rheaparekh rheaparekh commented Jun 14, 2018

Two slash commands:

  • /mute_topic: For this command, the usage is /mute_topic <topic> #<stream_name> and /mute_topic #<stream_name> <topic>. Both would work. The muting occurs using muting_ui in the frontend, where as the server side code checks if the stream and topic to be muted exists.
    Added tests for the server side code as well.
  • /settings

@@ -60,6 +60,7 @@ function update_setting(command) {
exports.process = function (message_content) {

var content = message_content.trim();
var tokens = content.split(' ');
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

A better name for tokens can be suggested.

Copy link
Member

Choose a reason for hiding this comment

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

tokens seems fine here.

msg = ("A valid topic name is required.")
else:
if topic_is_muted(user_profile, stream.id, topic):
msg = ("The topic '%s' is already muted." % (topic))
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

If the user tries to mute a topic which has already been muted, should we unmute it? Slack does this.

Copy link
Member

Choose a reason for hiding this comment

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

I think this should be the case since hotkey M does that.

Copy link
Member

@lonerz lonerz left a comment

Choose a reason for hiding this comment

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

model: Add helper function to check if topic exists. commit looks good. Other commits have minor comments.

@@ -60,6 +60,7 @@ function update_setting(command) {
exports.process = function (message_content) {

var content = message_content.trim();
var tokens = content.split(' ');
Copy link
Member

Choose a reason for hiding this comment

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

tokens seems fine here.

msg = ("A valid topic name is required.")
else:
if topic_is_muted(user_profile, stream.id, topic):
msg = ("The topic '%s' is already muted." % (topic))
Copy link
Member

Choose a reason for hiding this comment

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

I think this should be the case since hotkey M does that.

@@ -84,6 +84,10 @@ exports.process = function (message_content) {
return true;
}

if (content === '/settings') {
window.location.hash = 'settings/your-account';
Copy link
Member

Choose a reason for hiding this comment

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

Hm, not sure if this is the correct thing here. One part of me says this works (though seems hacky). Is there a way to open the modal with a function call?

Also, remember to return true;.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, at first, this seems hacky to me too(I guessed overlays.open_settings does that but it only open overlays), but now I think this should work properly because we have our own event handler for hashchange which handles this properly and I also think this is the only way to make this work.
@showell can clear this!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I agree with @shubhamdhama. I tried doing this with a function call, but It was working. (Mainly by calling settings.setup_page()). Even in settings menu this is being done.

@rheaparekh rheaparekh force-pushed the slash_command branch 3 times, most recently from 8afecf4 to 76cb159 Compare June 15, 2018 12:46
@zulipbot zulipbot added size: XL and removed size: L labels Jun 15, 2018
@rheaparekh rheaparekh changed the title [WIP] slash commands: Create basic slash commands using zcommands. slash commands: Create basic slash commands using zcommands. Jun 15, 2018
@zulipbot zulipbot added size: L and removed size: XL labels Jun 15, 2018
Copy link
Contributor

@neiljp neiljp left a comment

Choose a reason for hiding this comment

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

The basics are here, though I think there are some possible issues for the edge cases for stream and topic names. Other aspects such as what counts as a 'match' (case-sensitivity, sub-strings), providing help, and muting based on 'position', we can discuss on czo first, perhaps for v2?

In addition to what you have here, if a slash command doesn't get recognized, we don't currently show anything, it seems. You could add that in another commit here, or a separate PR?

@@ -1042,6 +1042,36 @@ def test_zcommand(self) -> None:
self.assert_json_success(result)
self.assertIn('still in day mode', result.json()['msg'])

payload = dict(command="mute_topic help")
Copy link
Contributor

Choose a reason for hiding this comment

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

I appreciate the idea of including help, but I'm not sure whether this works for slash commands - unless each does not normally take one parameter, or that parameter cannot be 'help'. Otherwise help for each command would be found inconsistently.

A separate /help or /helpslash command would be my personal preference, but that's to discuss on czo.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I have removed the help parameter for now.

result = self.client_post("/json/zcommand", payload)
self.assert_json_success(result)
self.assertIn("The topic 'Verona1' is already muted.", result.json()['msg'])

Copy link
Contributor

Choose a reason for hiding this comment

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

From the feedback, I assume you'll update this to have it toggle the mute, too?

Additionally, while I appreciate the flexibility of being able to mute an arbitrary topic, and this looks good as a v1, I wonder whether the following might be useful to include from a user's POV, as long as they can understand (and we can define) 'position':

  • /mute_topic -> stream&topic implied from current position
  • /mute_topic <topic> -> stream implied from current position, topic from <topic>
  • /mute topic <stream> <topic> -> as above :)

return json_success(ret)

topic = ' '.join(tokens[2:])
stream_name = tokens[1]
Copy link
Contributor

Choose a reason for hiding this comment

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

These two lines concern me slightly, though that might be from lack of knowledge of the server representation of streams and topics, and what is allowed in each: (see my suggestion re tests above too)

  • can streams not contain spaces too?
  • could we use # to identify if a stream/topic identifier from the command is intended to be a stream or not?
  • could we use another character/separate to identify a topic?

If we can make this distinction, then this might allow for a case in addition to those I listed above, along the lines of:

  • /mute_topic #<stream> -> mute #<stream>

In that case, we can shorten the command to just /mute, or maybe /mute should be reserved for streams.

@@ -706,6 +709,30 @@ def zcommand_backend(request: HttpRequest, user_profile: UserProfile,
ret = dict(msg=msg)
return json_success(ret)

if tokens[0] == 'mute_topic':
if len(tokens) < 3 or tokens[1] == 'help':
Copy link
Contributor

Choose a reason for hiding this comment

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

If we retain 'help', should this be tokens[1:]?

@@ -684,6 +684,9 @@ def find_first_unread_anchor(sa_conn: Any,
@has_request_variables
def zcommand_backend(request: HttpRequest, user_profile: UserProfile,
command: str=REQ('command')) -> HttpResponse:

tokens = command.split(' ')
Copy link
Contributor

Choose a reason for hiding this comment

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

This is only used in your new commands, so this should likely move to line ~711?

result = self.client_post("/json/zcommand", payload)
self.assert_json_success(result)
self.assertIn('A valid stream name is required.', result.json()['msg'])

Copy link
Contributor

Choose a reason for hiding this comment

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

I suggest you add some tests for topics, and maybe streams, which have spaces in them.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Updated the commit and added these tests.

do_mute_topic(user_profile, stream, recipient, topic)
msg = ("The topic '%s' has been muted." % (topic))
except ObjectDoesNotExist:
msg = ('A valid stream name is required.')
Copy link
Contributor

Choose a reason for hiding this comment

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

All of this uses exact matching (case-sensitive, word for word); this seems challenging for longer stream and/or topic names.
(particularly since I don't think we have a shortcut key for previous message, like 'up arrow' in some chat clients, to grab the previous text to edit a typo)

@showell
Copy link
Contributor

showell commented Jun 17, 2018

I didn't read this super closely, but it seems like we are introducing a lot of server code for muting, when we already have decent support for muting in the client already with the hotkey and menu options. It would be nice to have a super simple V1 to play with, as some of Neil's feedback is pointing to.

@showell
Copy link
Contributor

showell commented Jun 17, 2018

@timabbott I'm not sure what the best way to open the settings menu is, but it seems like setting window.location.hash mimics what happens when you go through the gear menu, and that's how @rheaparekh coded it. It appears that the info overlays work differently, for some reason. Do you know all the history behind that? It seems like it would be nice to have a less hacky way to open settings than setting the href (even if V1 was just a function in gear_menu.js called open_settings() or something).

Copy link
Contributor

@neiljp neiljp left a comment

Choose a reason for hiding this comment

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

In addition to my few comments (made recently, but the review didn't go through), there's also potential to use the approaches in the other zcommand PR?


def process_zcommands(content: str, user_profile: UserProfile) -> Dict[str, Any]:
if not content.startswith('/'):
raise JsonableError(_('zcommand does not have leading slash.'))
command = content[1:]
raise JsonableError(_('There should be a leading slash in the zcommand.'))
Copy link
Contributor

Choose a reason for hiding this comment

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

Strictly, this change in text should be in the previous commit?

command: content,
on_success: function (data) {
if (data.topic && data.stream) {
if (muting.is_topic_muted(data.stream, data.topic)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you use muting_ui here? That has a built-in toggle_mute. I'm not familiar with the front-end, though.

ret = dict(topic=topic, stream=stream_name)
else:
ret = dict(msg="A valid topic name is required.")
except ObjectDoesNotExist:
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this only get raised from get_realm_stream? If so, this might be clearer with the try/except wrapping only that, and maybe an extra else clause.

@rheaparekh rheaparekh force-pushed the slash_command branch 4 times, most recently from 01460d3 to 01ec7fd Compare June 27, 2018 13:51
@rheaparekh
Copy link
Collaborator Author

I have updated this PR.

This functions helps to extract the topic name from the mute_topic command
"""
stream_link = ("#**%s**" % (stream_name))
topic_name = content.replace('mute_topic', '').replace(stream_link, '')
Copy link
Contributor

Choose a reason for hiding this comment

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

This feels like a slightly brittle way to get the topic. I feel we want a single function that uses a regex to get both values, where the regex looks something like '#\*\*(.*)\*\* (.*).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I am already using possible_linked_stream_names from bugdown to extract the stream name. What I am doing here is removing both, the stream mention (which is in the format #**stream_name**)and mute_topic from the command(which is in the format /mute_topic #**stream_name** topic), hence we would have only topic left in the command.

@showell
Copy link
Contributor

showell commented Jul 2, 2018

I merged two of these commands, so you'll need to rebase.

@rheaparekh
Copy link
Collaborator Author

I have rebased this!

@showell showell changed the title slash commands: Create basic slash commands using zcommands. slash commands: Create /mute_topic Jul 2, 2018
@showell
Copy link
Contributor

showell commented Jul 2, 2018

Thanks for rebasing. I edited the PR title to reflect that it's just /mute_topic now.

Copy link
Contributor

@neiljp neiljp left a comment

Choose a reason for hiding this comment

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

LGTM excepting the points I noted; I'll leave it to @showell to comment further re any other updates and re your response to the earlier regex discussion.

return dict(msg="Usage: /mute_topic #<stream_name> <topic_name>")

[stream_name] = linked_stream
topic = get_topic_from_mute_topic_content(content, stream_name)
Copy link
Contributor

Choose a reason for hiding this comment

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

Minor: Reiterating what I said previously or before, linked_stream[0] might be simpler?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@neiljp linked_stream is a set containing only one element. So I can user list(linked_stream)[0], but I thought this looks much more cleaner.

return dict(msg='A valid stream name is required.')

if topic_exists(topic, get_stream_recipient(stream.id)):
ret = dict(subject=topic, stream=stream_name, type='stream')
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a reason you are passing type? This seems unnecessary.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The data passed in toggle_mute requires a type='stream'.

zerver/models.py Outdated
@@ -1309,6 +1309,12 @@ def get_context_for_message(message: Message) -> Sequence[Message]:
pub_date__gt=message.pub_date - timedelta(minutes=15),
).order_by('-id')[:10]

def topic_exists(topic_name: str, recipient: Recipient) -> bool:
topic_exists = Message.objects.filter(
subject=topic_name,
Copy link
Sponsor Member

@timabbott timabbott Jul 10, 2018

Choose a reason for hiding this comment

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

This logic is wrong; topics are always matched in a case-insensitive fashion.

So we should do subject__iexact= here.

(Edit; fixed a typo that said icontains -- that's for case-insensitive substrings)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed!

content="whatever", topic_name="my topic")
stream = get_realm_stream("Denmark", user_profile.realm.id)
recipient = get_stream_recipient(stream.id)
self.assertTrue(topic_exists('my topic', recipient))
Copy link
Sponsor Member

Choose a reason for hiding this comment

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

Let's add a test using differnet case (e.g. "My Topic"), which should pass.

@timabbott
Copy link
Sponsor Member

I posted one comment, but there's a deeper bug, which is that space isn't a good separator for stream/topic names, since spaces are valid characters in those. I think we might need to do something more like Zulip's *-mention syntax for a stream to encode the stream name (might be able to reuse the typeahead as well).

@rheaparekh rheaparekh force-pushed the slash_command branch 3 times, most recently from 9968e9d to 7377a1d Compare July 13, 2018 20:40
The muting occurs using muting_ui in the frontend,
where as the server side code checks if the stream
and topic to be muted exists.

Added tests for the server side code as well.
@rheaparekh
Copy link
Collaborator Author

I have updated this PR with the mentioned changes!

@neiljp
Copy link
Contributor

neiljp commented Jul 18, 2018

I thought we'd discussed spaces as an issue previously, but this looks to fix it fine now.

As a followup PR, I'd be inclined to suggest simplifying the tests, which are fairly concise up to night/day, but these now involve quite a lot of repetition.

@zulipbot
Copy link
Member

Heads up @rheaparekh, we just merged some commits that conflict with the changes your made in this pull request! You can review this repository's recent commits to see where the conflicts occur. Please rebase your feature branch against the upstream/master branch and resolve your pull request's merge conflicts accordingly.

@alya
Copy link
Contributor

alya commented Jun 9, 2023

Thanks for the work here @rheaparekh and reviewers! Closing as we have no current product plans to add this feature. Keyboard can mute a topic via the Shift+M shortcut.

@alya alya closed this Jun 9, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants