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

Flask server for interacting with the contrib bots through outgoing webhooks. #4864

Merged
merged 1 commit into from
Jun 20, 2017

Conversation

vabs22
Copy link
Contributor

@vabs22 vabs22 commented May 22, 2017

No description provided.

@vabs22
Copy link
Contributor Author

vabs22 commented May 22, 2017

Things to keep in mind:

  1. The flask server needs to be run inside the vagrant machine.
  2. It supports followup bot for now, the followup bot config details need to be added in the form specified in comments, in bot_config dict().
  3. To make it work for other bots, the base_url in populate_db changes need to be updated to http://127.0.0.1:5002/bots/<botname> and the corresponding config of bot needs to be added into bot_config dict().

Edit: updated server address

Copy link
Contributor

@robot-dreams robot-dreams left a comment

Choose a reason for hiding this comment

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

(Edit): Oops, I left a comment in the wrong window!

create_users(zulip_realm, zulip_outgoing_bots, bot_type=UserProfile.OUTGOING_WEBHOOK_BOT)
Service.objects.create(name="test",
user_profile=get_user_profile_by_email("outgoing-webhook@zulip.com"),
base_url="http://0.0.0.0:5002/bots/followup",
Copy link
Sponsor Member

Choose a reason for hiding this comment

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

Why is this 0.0.0.0:5002? Seems like maybe we want it to be 127.0.0.1:5002 instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

okay

@vabs22 vabs22 force-pushed the flaskserverbots branch 3 times, most recently from c92248e to 7643b6d Compare May 28, 2017 19:40
@vabs22 vabs22 changed the title (WIP) Flask server for interacting with the contrib bots through outgoing webhooks Flask server for interacting with the contrib bots through outgoing webhooks. May 28, 2017
@timabbott
Copy link
Sponsor Member

I merged a tweaked version of the outgoing webhook commit, so the basic outgoing webhook user should be available in the test suite now.

I think we should do a bit of cleanup of the flask server system next. I think the main thing that this needs is a configuration system that we can use to move the API keys and other settings out of the flask server code and into a config file.

I would suggest using a ConfigParser / zuliprc file style approach, similar to what our other bots do, though it might make sense to have a variation in the approach to support having multiple bots defined in the same file?

Copy link
Contributor

@robot-dreams robot-dreams 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 merging the test commit, @timabbott ! @vabs22 , I think you can rebase so that this PR only has the Flask commit.

I left a few comments inline; otherwise, I agree with working on the configuration next. Please feel free to PM me if you want to discuss the format for configuration files, or if you need help with getting the ConfigParser to work.

client=restricted_client,
state_handler=state_handler)

# return json_success("Success!")
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be "Success!" or json_success("Success!")? Either way, can you please choose one and then remove the comment?

path = "bots/" + str(bot) + "/" + str(bot) + ".py"
bots_lib_module[bot] = get_lib_module(path)

class StateHandler(object):
Copy link
Contributor

Choose a reason for hiding this comment

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

I think StateHandler has been extracted and should be available now, without needing to redefine it here.

def test(bot):
# type: (str) -> str
# if bot not in available_bots:
# return "requested bot service not supported"
Copy link
Contributor

Choose a reason for hiding this comment

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

In general, it's helpful for error messages to include more information about the error. In this case, for example, I would prefer something like this:

return "requested bot service {} not supported".format(bot)

return ""

@app.route('/bots/<bot>', methods=['POST'])
def test(bot):
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Is test the name you want here?

@app.route('/bots/<bot>', methods=['POST'])
def test(bot):
# type: (str) -> str
# if bot not in available_bots:
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 please uncomment or remove?

state_handler = StateHandler()

event = json.loads(request.data)
message_handler.handle_message(message=event["message"],
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you think it would be helpful to add a comment about the expected format of the request somewhere (i.e. the fact that "message" is the relevant key, as well as the expected format of event["message"])?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah it would be helpful to add the comment. Currently we are sending the data directly but later on I think we would be passing it through an interface designed for this server. That might be the best place for it. Do you want me to add a TODO comment inside DoRestCall?

Copy link
Contributor

Choose a reason for hiding this comment

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

That sounds good for now.

@vabs22
Copy link
Contributor Author

vabs22 commented May 30, 2017

@timabbott @robot-dreams Thanks for you reviews!. I have incorporated the comments and updated it.
I also think it would be reasonable to have a single file containing the configurations of all bots. I have completed an intial version for the same and added a new commit which generates a single config file for all active bots. The sample format of the config file is as above:

screenshot from 2017-05-30 23-16-46

For downloading the above file I have added a download button similar to existing one. It can be seen here:

screenshot from 2017-05-30 22-56-43

I have also updated the flask server to read the configuration file and then populate the bots_config dict using SafeConfigParser. Please share your thoughts.

@vabs22 vabs22 force-pushed the flaskserverbots branch 2 times, most recently from 5a720dd to 39b5e16 Compare May 30, 2017 20:09
Copy link
Contributor

@robot-dreams robot-dreams left a comment

Choose a reason for hiding this comment

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

Thanks, this is a nice approach to configuration! I left a few comments inline; please take a look.

@@ -56,6 +56,14 @@ exports.generate_zuliprc_content = function (email, api_key) {
"\n";
};

exports.generate_zuliprc_content_for_single_file = function (email, api_key) {
return "[" + email.substring(0, email.indexOf("-bot@zulip.localhost")) + "]" +
Copy link
Contributor

Choose a reason for hiding this comment

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

Will this always be @zulip.localhost? Would it make sense to use email.indexOf("-bot@") instead?

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, would this code be more readable with a separate bot_name_from_email function?

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 agree with both of your points.

@@ -59,6 +59,13 @@

<div class="form-horizontal" id="api_key_button_box">
<div class="input-group side-padded-container">
<div>
<span>Download single zuliprc of all active bots</span>
Copy link
Contributor

Choose a reason for hiding this comment

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

Could the zuliprc name cause confusion because it's also used for other kinds of configuration files?

If so, would a name like zulipbotrc or flaskbotrc or (you can probably come up with something better) make sense?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah flaskbotrc would make more sense to the users.

app = Flask(__name__)

@app.route('/')
def main():
Copy link
Contributor

Choose a reason for hiding this comment

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

Just checking, is it necessary to define this route / function?

If so, can it be called something besides main()?
If not, can it be removed?

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 had added this initially for testing, so it can be removed now. I should have removed it much earlier. My bad.

state_handler = StateHandler()

event = json.loads(request.data)
message_handler.handle_message(message=event["message"],
Copy link
Contributor

Choose a reason for hiding this comment

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

That sounds good for now.


from zulip import Client

config_file_path = sys.argv[1]
Copy link
Contributor

Choose a reason for hiding this comment

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

Referencing sys.argv[1] at the top level like this seems like it could lead to confusion or problems—especially if this module gets imported somewhere else.

While this is pretty clearly a standalone app and probably won't ever be imported, I still think it's important to consistently use good habits across the codebase.

Instead of the current approach, do you think it would make sense to define a load_config function that takes a file_path as a parameter, and then call it from the if __name__ == "__main__" block?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah it would be much better to shift these code lines into separate functions and call them from if __name__ == "__main__" block.

site=bots_config[bot]["site"])
restricted_client = BotHandlerApi(client)
message_handler = bots_lib_module[bot].handler_class()
state_handler = StateHandler()
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 please add a TODO to handle stateful bots (e.g. the Tic Tac Toe bot)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

okay

@@ -59,6 +59,13 @@

<div class="form-horizontal" id="api_key_button_box">
<div class="input-group side-padded-container">
<div>
<span>Download single zuliprc of all active bots</span>
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be helpful to say what this is for (e.g. Download config of all active bots for Flask bot server or something)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah, I agree.

Copy link
Contributor

@robot-dreams robot-dreams left a comment

Choose a reason for hiding this comment

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

Looks good to me. Thanks for addressing all of my comments! I can't wait to see this in action.

@timabbott
Copy link
Sponsor Member

timabbott commented Jun 13, 2017

@vabs22 I merged the first commit to help make it easy for other folks to collaborate on the flask system, congrats on getting that done! I noticed a few things we'll want to fix in the near future:

  • python api/flask_bot_server.py doesn't work; you have to run it directly from api/. Since with the pip installer, this will be available from just about any directory, we definitely will want to work on this element.
  • Structurally, flask_api_server.py might belong in the api/zulip directory; I'm not sure, and possibly also should be executable. I didn't move it when merging because its path handling is fragile (see above) and I didn't want to debug potential issues there before making it possible for multiple folks to work on this code easily.
  • We should use optparse/argparse to make --help work, so it's easy to figure it out.
  • We might want to rename it (at least when installed) to have a reasonable name in PATH. I.e. I think we kinda want the command after a pip install to be zulip-bot-server ~/flaskbotrc.
  • The error message if your flaskbotrc file has as invalid bot name is super confusing. We should be really clear that the problem is that there is no bot with name "test" if your bot is called "test-bot", and that the most likely solution is to edit your flaskbotrc file to fix the name of the bot to match the bot you want to run.

@eeshangarg, as the maintainer of the pip package, can you collaborate with @vabs22 on these follow-up tasks (which I think we should try to get done this week, since they should mostly be quick.).

More medium-term, we will want to work on these:

  • Add a basic test suite for the Flask system so we can change it with minimal fear.
  • supervisord configuration for running the flask bot persistently that's super easy to setup (e.g. install-zulip-flask-supervisord <path_to_flaskbotrc>
  • We should work on writing trivial heroku instructions for using this.

@vabs22 can you and @robot-dreams open issues for all of the above follow-up tasks that aren't going to be done this week? I imagine those will be your projects for the next week or two.

For the JS changes, I opened #5355 for a related cleanup we'll want to do, which would be reasonable for you to work on @vabs22. The remaining patch in this PR I can merge once we fix a couple small things:

  • "Download config of all active bots for Flask bot server" needs to be tagged for translation
  • "Download config of all active bots for Flask bot server" is confusing. I think we want to name the "Flask bot server" something more suggestive, e.g. "Zulip Botserver" (I don't think our users should care that's based on Flask). Also, it should probably only download the outgoing webhook bots, since e.g. incoming webhooks aren't useful to put in this flask server. So maybe something like "Download config of all active outgoing webhooks in Zulip Botserver format."

@vabs22
Copy link
Contributor Author

vabs22 commented Jun 16, 2017

I have updated this PR, the UI looks like:
screenshot from 2017-06-16 15-46-11
Once the outgoing webhook UI gets merged, I will update the tests.

@showell
Copy link
Contributor

showell commented Jun 19, 2017

@vabs22 Can you clean up the commit message here? The title line should be no longer than 65 characters, and then you can have an empty line and more text to elaborate as needed.

Flaskbotrc is a file containing config of all active
outgoing webhook bots. It is used to provide configuration
of all active outgoing webhook bots to zulip-bot-server.
@vabs22
Copy link
Contributor Author

vabs22 commented Jun 20, 2017

@showell I have updated the commit message and the tests. Please take a look.

@showell showell merged commit 9a6e326 into zulip:master Jun 20, 2017
@showell
Copy link
Contributor

showell commented Jun 20, 2017

Merged! Thanks, @vabs22!

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.

None yet

5 participants