interactive bots: Create idonethis bot.
|
@ -80,7 +80,9 @@ force_include = [
|
||||||
"zulip_bots/zulip_bots/bots/baremetrics/baremetrics.py",
|
"zulip_bots/zulip_bots/bots/baremetrics/baremetrics.py",
|
||||||
"zulip_bots/zulip_bots/bots/baremetrics/test_baremetrics.py",
|
"zulip_bots/zulip_bots/bots/baremetrics/test_baremetrics.py",
|
||||||
"zulip_bots/zulip_bots/bots/salesforce/salesforce.py",
|
"zulip_bots/zulip_bots/bots/salesforce/salesforce.py",
|
||||||
"zulip_bots/zulip_bots/bots/salesforce/test_salesforce.py"
|
"zulip_bots/zulip_bots/bots/salesforce/test_salesforce.py",
|
||||||
|
"zulip_bots/zulip_bots/bots/idonethis/idonethis.py",
|
||||||
|
"zulip_bots/zulip_bots/bots/idonethis/test_idonethis.py"
|
||||||
]
|
]
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description="Run mypy on files tracked by git.")
|
parser = argparse.ArgumentParser(description="Run mypy on files tracked by git.")
|
||||||
|
|
0
zulip_bots/zulip_bots/bots/idonethis/__init__.py
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
zulip_bots/zulip_bots/bots/idonethis/assets/idonethis-help.png
Normal file
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 12 KiB |
46
zulip_bots/zulip_bots/bots/idonethis/doc.md
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
# idonethis bot
|
||||||
|
|
||||||
|
The idonethis bot is a Zulip bot which allows interaction with [idonethis](https://idonethis.com/)
|
||||||
|
through Zulip. It can peform actions such as viewing teams, list entries and creating entries.
|
||||||
|
|
||||||
|
To use the bot simply @-mention the bot followed by a specific command. See the usage section
|
||||||
|
below for a list of available commands.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
Before proceeding further, ensure you have an idonethis account.
|
||||||
|
|
||||||
|
1. Go to [your idonethis settings](https://beta.idonethis.com/u/settings), scroll down
|
||||||
|
and copy your API token.
|
||||||
|
2. Open up `zulip_bots/bots/idonethis/idonethis.conf` in your favorite editor, and change
|
||||||
|
`api_key` to your API token.
|
||||||
|
3. Optionally, change the `default_team` value to your default team for creating new messages.
|
||||||
|
If this is not specified, a team will be required to be manually specified every time an entry is created.
|
||||||
|
|
||||||
|
Run this bot as described [here](https://zulipchat.com/api/running-bots#running-a-bot).
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
`<team>` can either be the name or ID of a team.
|
||||||
|
|
||||||
|
* `@mention help` view this help message.
|
||||||
|
![](/static/generated/bots/idonethis/assets/idonethis-help.png)
|
||||||
|
* `@mention teams list` or `@mention list teams`
|
||||||
|
List all the teams.
|
||||||
|
![](/static/generated/bots/idonethis/assets/idonethis-list-teams.png)
|
||||||
|
* `@mention team info <team>`.
|
||||||
|
Show information about one `<team>`.
|
||||||
|
![](/static/generated/bots/idonethis/assets/idonethis-team-info.png)
|
||||||
|
* `@mention entries list` or `@mention list entries`.
|
||||||
|
List entries from any team
|
||||||
|
![](/static/generated/bots/idonethis/assets/idonethis-entries-all-teams.png)
|
||||||
|
* `@mention entries list <team>` or `@mention list entries <team>`
|
||||||
|
List all entries from `<team>`.
|
||||||
|
![](/static/generated/bots/idonethis/assets/idonethis-list-entries-specific-team.png)
|
||||||
|
* `@mention entries create` or `@mention new entry` or `@mention create entry`
|
||||||
|
or `@mention new entry` or `@mention i did`
|
||||||
|
Create a new entry. Optionally supply `--team=<team>` for teams with no spaces or `"--team=<team>"`
|
||||||
|
for teams with spaces. For example `@mention i did "--team=product team" something` will create a
|
||||||
|
new entry `something` for the product team.
|
||||||
|
![](/static/generated/bots/idonethis/assets/idonethis-new-entry.png)
|
||||||
|
![](/static/generated/bots/idonethis/assets/idonethis-new-entry-specific-team.png)
|
13
zulip_bots/zulip_bots/bots/idonethis/fixtures/api_noop.json
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"request": {
|
||||||
|
"api_url": "https://beta.idonethis.com/api/v2/noop",
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Token 12345678"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": {"doesnt": "matter"},
|
||||||
|
"response-headers": {
|
||||||
|
"status": 200,
|
||||||
|
"content-type": "application/json; charset=utf-8"
|
||||||
|
}
|
||||||
|
}
|
26
zulip_bots/zulip_bots/bots/idonethis/fixtures/team_list.json
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"request": {
|
||||||
|
"api_url": "https://beta.idonethis.com/api/v2/teams",
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Token 12345678"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": [
|
||||||
|
{
|
||||||
|
"name": "testing team 1",
|
||||||
|
"created_at": "2017-12-28T19:12:24.977+11:00",
|
||||||
|
"updated_at": "2017-12-28T19:12:24.977+11:00",
|
||||||
|
"hash_id": "31415926535"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "test_team_2",
|
||||||
|
"created_at": "2017-12-28T19:12:55.121+11:00",
|
||||||
|
"updated_at": "2017-12-28T19:12:55.121+11:00",
|
||||||
|
"hash_id": "8979deadbeef"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"response-headers": {
|
||||||
|
"status": 200,
|
||||||
|
"content-type": "application/json; charset=utf-8"
|
||||||
|
}
|
||||||
|
}
|
15
zulip_bots/zulip_bots/bots/idonethis/fixtures/test_401.json
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"request": {
|
||||||
|
"api_url": "https://beta.idonethis.com/api/v2/teams",
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Token 87654321"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"error": "Invalid API Authentication"
|
||||||
|
},
|
||||||
|
"response-headers": {
|
||||||
|
"status": 401,
|
||||||
|
"content-type": "application/json; charset=utf-8"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
{
|
||||||
|
"request": {
|
||||||
|
"api_url": "https://beta.idonethis.com/api/v2/entries",
|
||||||
|
"method": "POST",
|
||||||
|
"json": {
|
||||||
|
"body": "something and something else",
|
||||||
|
"team_id": "31415926535"
|
||||||
|
},
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Token 12345678"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"body": "something and something else",
|
||||||
|
"created_at": "2018-01-04T20:07:58.078+11:00",
|
||||||
|
"updated_at": "2018-01-04T20:07:58.078+11:00",
|
||||||
|
"occurred_on": "2018-01-04",
|
||||||
|
"status": "done",
|
||||||
|
"hash_id": "fa974ad8c1acb9e81361a051a697f9dae22908d6",
|
||||||
|
"completed_on": null,
|
||||||
|
"archived_at": null,
|
||||||
|
"body_formatted": "something and something else",
|
||||||
|
"team": {
|
||||||
|
"name": "testing team 1",
|
||||||
|
"hash_id": "31415926535"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"email_address": "xavier.cooney03@gmail.com",
|
||||||
|
"full_name": "Benji Franklin",
|
||||||
|
"hash_id": "f22a944f"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response-headers": {
|
||||||
|
"status": 200,
|
||||||
|
"content-type": "application/json; charset=utf-8"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
{
|
||||||
|
"request": {
|
||||||
|
"api_url": "https://beta.idonethis.com/api/v2/entries",
|
||||||
|
"method": "POST",
|
||||||
|
"json": {
|
||||||
|
"body": "something and something else",
|
||||||
|
"team_id": "8979deadbeef"
|
||||||
|
},
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Token 12345678"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"body": "something and something else",
|
||||||
|
"created_at": "2018-01-04T20:07:58.078+11:00",
|
||||||
|
"updated_at": "2018-01-04T20:07:58.078+11:00",
|
||||||
|
"occurred_on": "2018-01-04",
|
||||||
|
"status": "done",
|
||||||
|
"hash_id": "fa974ad8c1acb9e81361a051a697f9dae22908d6",
|
||||||
|
"completed_on": null,
|
||||||
|
"archived_at": null,
|
||||||
|
"body_formatted": "something and something else",
|
||||||
|
"team": {
|
||||||
|
"name": "test_team_2",
|
||||||
|
"hash_id": "8979deadbeef"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"email_address": "xavier.cooney03@gmail.com",
|
||||||
|
"full_name": "Benji Franklin",
|
||||||
|
"hash_id": "f22a944f"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response-headers": {
|
||||||
|
"status": 200,
|
||||||
|
"content-type": "application/json; charset=utf-8"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
{
|
||||||
|
"request": {
|
||||||
|
"api_url": "https://beta.idonethis.com/api/v2/entries?team_id=31415926535",
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Token 12345678"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": [
|
||||||
|
{
|
||||||
|
"body": "TESTING<>",
|
||||||
|
"created_at": "2018-01-04T21:10:13.084+11:00",
|
||||||
|
"updated_at": "2018-01-04T21:10:13.084+11:00",
|
||||||
|
"occurred_on": "2018-01-04",
|
||||||
|
"status": "done",
|
||||||
|
"hash_id": "65e1b21fd8f63adede1daae0bdf28c0e47b84923",
|
||||||
|
"completed_on": null,
|
||||||
|
"archived_at": null,
|
||||||
|
"body_formatted": "TESTING",
|
||||||
|
"team": {
|
||||||
|
"name": "testing team 1",
|
||||||
|
"hash_id": "31415926535"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"email_address": "john.doe@generic.name",
|
||||||
|
"full_name": "John Doe",
|
||||||
|
"hash_id": "deadbeef"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"body": "Grabbing some more data...",
|
||||||
|
"created_at": "2018-01-04T20:07:58.078+11:00",
|
||||||
|
"updated_at": "2018-01-04T20:07:58.078+11:00",
|
||||||
|
"occurred_on": "2018-01-04",
|
||||||
|
"status": "done",
|
||||||
|
"hash_id": "fa974ad8c1acb9e81361a051a697f9dae22908d6",
|
||||||
|
"completed_on": null,
|
||||||
|
"archived_at": null,
|
||||||
|
"body_formatted": "Grabbing some more data...",
|
||||||
|
"team": {
|
||||||
|
"name": "testing team 1",
|
||||||
|
"hash_id": "31415926535"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"email_address": "john.doe@generic.name",
|
||||||
|
"full_name": "John Doe",
|
||||||
|
"hash_id": "deadbeef"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"body": "GRABBING HTTP DATA",
|
||||||
|
"created_at": "2018-01-04T19:07:17.214+11:00",
|
||||||
|
"updated_at": "2018-01-04T19:07:17.214+11:00",
|
||||||
|
"occurred_on": "2018-01-04",
|
||||||
|
"status": "done",
|
||||||
|
"hash_id": "72c8241d2218464433268c5abd6625ac104e3d8f",
|
||||||
|
"completed_on": null,
|
||||||
|
"archived_at": null,
|
||||||
|
"body_formatted": "GRABBING HTTP DATA",
|
||||||
|
"team": {
|
||||||
|
"name": "testing team 1",
|
||||||
|
"hash_id": "31415926535"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"email_address": "john.doe@generic.name",
|
||||||
|
"full_name": "John Doe",
|
||||||
|
"hash_id": "deadbeef"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"response-headers": {
|
||||||
|
"status": 200,
|
||||||
|
"content-type": "application/json; charset=utf-8"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"request": {
|
||||||
|
"api_url": "https://beta.idonethis.com/api/v2/teams/31415926535",
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Token 12345678"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"hash_id": "31415926535",
|
||||||
|
"name": "testing team 1",
|
||||||
|
"created_at": "2017-12-28T19:12:55.121+11:00",
|
||||||
|
"updated_at": "2017-12-28T19:12:55.121+11:00"
|
||||||
|
},
|
||||||
|
"response-headers": {
|
||||||
|
"status": 200,
|
||||||
|
"content-type": "application/json; charset=utf-8"
|
||||||
|
}
|
||||||
|
}
|
3
zulip_bots/zulip_bots/bots/idonethis/idonethis.conf
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[idonethis]
|
||||||
|
api_key = 45ba63047f8edbd0ddb9531bfa0971dd1e575313
|
||||||
|
default_team = product team
|
217
zulip_bots/zulip_bots/bots/idonethis/idonethis.py
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
import sys
|
||||||
|
import requests
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
|
||||||
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
API_BASE_URL = "https://beta.idonethis.com/api/v2"
|
||||||
|
|
||||||
|
api_key = ""
|
||||||
|
default_team = ""
|
||||||
|
|
||||||
|
class AuthenticationException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class TeamNotFoundException(Exception):
|
||||||
|
def __init__(self, team: str) -> None:
|
||||||
|
self.team = team
|
||||||
|
|
||||||
|
class UnknownCommandSyntax(Exception):
|
||||||
|
def __init__(self, detail: str) -> None:
|
||||||
|
self.detail = detail
|
||||||
|
pass
|
||||||
|
|
||||||
|
class UnspecifiedProblemException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def make_API_request(endpoint: str, method: str="GET", body: Dict[str, str]=None) -> Any:
|
||||||
|
headers = {'Authorization': 'Token ' + api_key}
|
||||||
|
if method == "GET":
|
||||||
|
r = requests.get(API_BASE_URL + endpoint, headers=headers)
|
||||||
|
elif method == "POST":
|
||||||
|
r = requests.post(API_BASE_URL + endpoint, headers=headers, json=body)
|
||||||
|
if r.status_code == 200:
|
||||||
|
return r.json()
|
||||||
|
elif r.status_code == 401 and 'error' in r.json() and r.json()['error'] == "Invalid API Authentication":
|
||||||
|
logging.error('Error authenticating, please check key ' + str(r.url))
|
||||||
|
raise AuthenticationException()
|
||||||
|
else:
|
||||||
|
logging.error('Error make API request, code ' + str(r.status_code) + '. json: ' + r.json())
|
||||||
|
raise UnspecifiedProblemException()
|
||||||
|
|
||||||
|
def api_noop() -> Any:
|
||||||
|
return make_API_request("/noop")
|
||||||
|
|
||||||
|
def api_list_team() -> Any:
|
||||||
|
return make_API_request("/teams")
|
||||||
|
|
||||||
|
def api_show_team(hash_id: str) -> Any:
|
||||||
|
return make_API_request("/teams/{}".format(hash_id))
|
||||||
|
|
||||||
|
def api_show_users(hash_id: str) -> Any:
|
||||||
|
return make_API_request("/teams/{}/members".format(hash_id))
|
||||||
|
|
||||||
|
def api_list_entries(team_id: str=None) -> Any:
|
||||||
|
if team_id:
|
||||||
|
return make_API_request("/entries?team_id={}".format(team_id))
|
||||||
|
else:
|
||||||
|
return make_API_request("/entries".format(team_id))
|
||||||
|
|
||||||
|
def api_create_entry(body: str, team_id: str) -> Any:
|
||||||
|
return make_API_request("/entries", "POST", {"body": body, "team_id": team_id})
|
||||||
|
|
||||||
|
def list_steams() -> str:
|
||||||
|
data = api_list_team()
|
||||||
|
response = "Teams:\n"
|
||||||
|
for team in data:
|
||||||
|
response += " * " + team['name'] + "\n"
|
||||||
|
return response
|
||||||
|
|
||||||
|
def get_team_hash(team_name: str) -> str:
|
||||||
|
data = api_list_team()
|
||||||
|
for team in data:
|
||||||
|
if team['name'].lower() == team_name.lower() or team['hash_id'] == team_name:
|
||||||
|
return team['hash_id']
|
||||||
|
raise TeamNotFoundException(team_name)
|
||||||
|
|
||||||
|
def team_info(team_name: str) -> str:
|
||||||
|
data = api_show_team(get_team_hash(team_name))
|
||||||
|
response = "Team Name: " + data['name'] + "\n"
|
||||||
|
response += "ID: `" + data['hash_id'] + "`\n"
|
||||||
|
response += "Created at: " + data['created_at'] + "\n"
|
||||||
|
return response
|
||||||
|
|
||||||
|
def entries_list(team_name: str) -> str:
|
||||||
|
if team_name:
|
||||||
|
data = api_list_entries(get_team_hash(team_name))
|
||||||
|
response = "Entries for " + team_name + ":\n"
|
||||||
|
else:
|
||||||
|
data = api_list_entries()
|
||||||
|
response = "Entries for all teams:\n"
|
||||||
|
for entry in data:
|
||||||
|
response += " * " + entry['body_formatted'] + "\n"
|
||||||
|
response += " * Created at: " + entry['created_at'] + "\n"
|
||||||
|
response += " * Status: " + entry['status'] + "\n"
|
||||||
|
response += " * User: " + entry['user']['full_name'] + "\n"
|
||||||
|
response += " * Team: " + entry['team']['name'] + "\n"
|
||||||
|
response += " * ID: " + entry['hash_id'] + "\n"
|
||||||
|
return response
|
||||||
|
|
||||||
|
def create_entry(message: str) -> str:
|
||||||
|
SINGLE_WORD_REGEX = re.compile("--team=([a-zA-Z0-9_]*)")
|
||||||
|
MULTIWORD_REGEX = re.compile('"--team=([^"]*)"')
|
||||||
|
|
||||||
|
team = ""
|
||||||
|
new_message = ""
|
||||||
|
single_word_match = SINGLE_WORD_REGEX.search(message)
|
||||||
|
multiword_match = MULTIWORD_REGEX.search(message)
|
||||||
|
|
||||||
|
if multiword_match is not None:
|
||||||
|
team = multiword_match.group(1)
|
||||||
|
new_message = MULTIWORD_REGEX.sub("", message).strip()
|
||||||
|
elif single_word_match is not None:
|
||||||
|
team = single_word_match.group(1)
|
||||||
|
new_message = SINGLE_WORD_REGEX.sub("", message).strip()
|
||||||
|
elif default_team:
|
||||||
|
team = default_team
|
||||||
|
new_message = message
|
||||||
|
else:
|
||||||
|
raise UnknownCommandSyntax("""I don't know which team you meant for me to create an entry under.
|
||||||
|
Either set a default team or pass the `--team` flag.
|
||||||
|
More information in my help""")
|
||||||
|
|
||||||
|
team_id = get_team_hash(team)
|
||||||
|
data = api_create_entry(new_message, team_id)
|
||||||
|
return "Great work :thumbs_up:. New entry `{}` created!".format(data['body_formatted'])
|
||||||
|
|
||||||
|
class IDoneThisHandler(object):
|
||||||
|
def initialize(self, bot_handler: Any) -> None:
|
||||||
|
global api_key, default_team
|
||||||
|
self.config_info = bot_handler.get_config_info('idonethis')
|
||||||
|
if 'api_key' in self.config_info:
|
||||||
|
api_key = self.config_info['api_key']
|
||||||
|
else:
|
||||||
|
logging.error("An API key must be specified for this bot to run.")
|
||||||
|
logging.error("Have a look at the Setup section of my documenation for more information.")
|
||||||
|
bot_handler.quit()
|
||||||
|
|
||||||
|
if 'default_team' in self.config_info:
|
||||||
|
default_team = self.config_info['default_team']
|
||||||
|
else:
|
||||||
|
logging.error("Cannot find default team. Users will need to manually specify a team each time an entry is created.")
|
||||||
|
|
||||||
|
try:
|
||||||
|
api_noop()
|
||||||
|
except AuthenticationException:
|
||||||
|
logging.error("Authentication exception with idonethis. Can you check that your API keys are correct? ")
|
||||||
|
bot_handler.quit()
|
||||||
|
except UnspecifiedProblemException:
|
||||||
|
logging.error("Problem connecting to idonethis. Please check connection")
|
||||||
|
bot_handler.quit()
|
||||||
|
|
||||||
|
def usage(self) -> str:
|
||||||
|
default_team_message = ""
|
||||||
|
if default_team:
|
||||||
|
default_team_message = "The default team is currently set as `" + default_team + "`."
|
||||||
|
else:
|
||||||
|
default_team_message = "There is currently no default team set up :frowning:."
|
||||||
|
return '''
|
||||||
|
This bot allows for interaction with idonethis, a collaboration tool to increase a team's productivity.
|
||||||
|
Below are some of the commands you can use, and what they do.
|
||||||
|
|
||||||
|
`<team>` can either be the name or ID of a team.
|
||||||
|
|
||||||
|
* `@mention help` view this help message
|
||||||
|
* `@mention list teams`
|
||||||
|
List all the teams
|
||||||
|
* `@mention team info <team>`
|
||||||
|
Show information about one `<team>`
|
||||||
|
* `@mention list entries`
|
||||||
|
List entries from any team
|
||||||
|
* `@mention list entries <team>`
|
||||||
|
List all entries from `<team>`
|
||||||
|
* `@mention new entry` or `@mention i did`
|
||||||
|
Create a new entry. Optionally supply `--team=<team>` for teams with no spaces or `"--team=<team>"`
|
||||||
|
for teams with spaces. For example `@mention i did "--team=product team" something` will create a
|
||||||
|
new entry `something` for the product team.
|
||||||
|
''' + default_team_message
|
||||||
|
|
||||||
|
def handle_message(self, message: Any, bot_handler: Any) -> None:
|
||||||
|
bot_handler.send_reply(message, self.get_response(message))
|
||||||
|
|
||||||
|
def get_response(self, message: Any) -> str:
|
||||||
|
message_content = message['content'].strip().split()
|
||||||
|
if message_content == "":
|
||||||
|
return ""
|
||||||
|
reply = ""
|
||||||
|
try:
|
||||||
|
command = " ".join(message_content[:2])
|
||||||
|
if command in ["teams list", "list teams"]:
|
||||||
|
reply = list_steams()
|
||||||
|
elif command in ["teams info", "team info"]:
|
||||||
|
if len(message_content) > 2:
|
||||||
|
reply = team_info(" ".join(message_content[2:]))
|
||||||
|
else:
|
||||||
|
raise UnknownCommandSyntax("You must specify the team in which you request information from.")
|
||||||
|
elif command in ["entries list", "list entries"]:
|
||||||
|
reply = entries_list(" ".join(message_content[2:]))
|
||||||
|
elif command in ["entries create", "create entry", "new entry", "i did"]:
|
||||||
|
reply = create_entry(" ".join(message_content[2:]))
|
||||||
|
elif command in ["help"]:
|
||||||
|
reply = self.usage()
|
||||||
|
else:
|
||||||
|
raise UnknownCommandSyntax("I can't understand the command you sent me :confused: ")
|
||||||
|
except TeamNotFoundException as e:
|
||||||
|
reply = "Sorry, it doesn't seem as if I can find a team named `" + e.team + "` :frowning:."
|
||||||
|
except AuthenticationException:
|
||||||
|
reply = "I can't currently authenticate with idonethis. "
|
||||||
|
reply += "Can you check that your API key is correct? For more information see my documentation."
|
||||||
|
except UnknownCommandSyntax as e:
|
||||||
|
reply = "Sorry, I don't understand what your trying to say. Use `@mention help` to see my help. " + e.detail
|
||||||
|
except Exception as e: # catches UnspecifiedProblemException, and other problems
|
||||||
|
reply = "Oh dear, I'm having problems processing your request right now. Perhaps you could try again later :grinning:"
|
||||||
|
logging.error("Exception caught: " + str(e))
|
||||||
|
return reply
|
||||||
|
|
||||||
|
handler_class = IDoneThisHandler
|
93
zulip_bots/zulip_bots/bots/idonethis/test_idonethis.py
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from zulip_bots.test_lib import BotTestCase
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
class TestIDoneThisBot(BotTestCase):
|
||||||
|
bot_name = "idonethis" # type: str
|
||||||
|
|
||||||
|
def test_create_entry_default_team(self) -> None:
|
||||||
|
with self.mock_config_info({'api_key': '12345678', 'default_team': 'testing team 1'}), \
|
||||||
|
self.mock_http_conversation('test_create_entry'), \
|
||||||
|
self.mock_http_conversation('team_list'):
|
||||||
|
self.verify_reply('i did something and something else',
|
||||||
|
'Great work :thumbs_up:. New entry `something and something else` created!')
|
||||||
|
|
||||||
|
def test_create_entry_quoted_team(self) -> None:
|
||||||
|
with self.mock_config_info({'api_key': '12345678', 'default_team': 'test_team_2'}), \
|
||||||
|
self.mock_http_conversation('test_create_entry'), \
|
||||||
|
self.mock_http_conversation('team_list'):
|
||||||
|
self.verify_reply('i did something and something else "--team=testing team 1"',
|
||||||
|
'Great work :thumbs_up:. New entry `something and something else` created!')
|
||||||
|
|
||||||
|
def test_create_entry_single_word_team(self) -> None:
|
||||||
|
with self.mock_config_info({'api_key': '12345678', 'default_team': 'testing team 1'}), \
|
||||||
|
self.mock_http_conversation('test_create_entry_team_2'), \
|
||||||
|
self.mock_http_conversation('team_list'):
|
||||||
|
self.verify_reply('i did something and something else --team=test_team_2',
|
||||||
|
'Great work :thumbs_up:. New entry `something and something else` created!')
|
||||||
|
|
||||||
|
def test_bad_key(self) -> None:
|
||||||
|
with self.mock_config_info({'api_key': '87654321', 'default_team': 'testing team 1'}), \
|
||||||
|
self.mock_http_conversation('test_401'), \
|
||||||
|
patch('zulip_bots.bots.idonethis.idonethis.api_noop'), \
|
||||||
|
patch('logging.error'):
|
||||||
|
self.verify_reply('list teams',
|
||||||
|
'I can\'t currently authenticate with idonethis. Can you check that your API key is correct? '
|
||||||
|
'For more information see my documentation.')
|
||||||
|
|
||||||
|
def test_list_team(self) -> None:
|
||||||
|
with self.mock_config_info({'api_key': '12345678', 'default_team': 'testing team 1'}), \
|
||||||
|
self.mock_http_conversation('team_list'):
|
||||||
|
self.verify_reply('list teams',
|
||||||
|
'Teams:\n * testing team 1\n * test_team_2\n')
|
||||||
|
|
||||||
|
def test_show_team_no_team(self) -> None:
|
||||||
|
with self.mock_config_info({'api_key': '12345678', 'default_team': 'testing team 1'}), \
|
||||||
|
self.mock_http_conversation('api_noop'):
|
||||||
|
self.verify_reply('team info',
|
||||||
|
'Sorry, I don\'t understand what your trying to say. Use `@mention help` to see my help. '
|
||||||
|
'You must specify the team in which you request information from.')
|
||||||
|
|
||||||
|
def test_show_team(self) -> None:
|
||||||
|
with self.mock_config_info({'api_key': '12345678', 'default_team': 'testing team 1'}), \
|
||||||
|
self.mock_http_conversation('test_show_team'), \
|
||||||
|
patch('zulip_bots.bots.idonethis.idonethis.get_team_hash', return_value='31415926535') as get_team_hashFunction:
|
||||||
|
self.verify_reply('team info testing team 1',
|
||||||
|
'Team Name: testing team 1\n'
|
||||||
|
'ID: `31415926535`\n'
|
||||||
|
'Created at: 2017-12-28T19:12:55.121+11:00\n')
|
||||||
|
get_team_hashFunction.assert_called_with('testing team 1')
|
||||||
|
|
||||||
|
def test_entries_list(self) -> None:
|
||||||
|
with self.mock_config_info({'api_key': '12345678', 'default_team': 'testing team 1'}), \
|
||||||
|
self.mock_http_conversation('test_entries_list'), \
|
||||||
|
patch('zulip_bots.bots.idonethis.idonethis.get_team_hash', return_value='31415926535') as get_team_hashFunction:
|
||||||
|
self.verify_reply('entries list testing team 1',
|
||||||
|
'Entries for testing team 1:\n'
|
||||||
|
' * TESTING\n'
|
||||||
|
' * Created at: 2018-01-04T21:10:13.084+11:00\n'
|
||||||
|
' * Status: done\n'
|
||||||
|
' * User: John Doe\n'
|
||||||
|
' * Team: testing team 1\n'
|
||||||
|
' * ID: 65e1b21fd8f63adede1daae0bdf28c0e47b84923\n'
|
||||||
|
' * Grabbing some more data...\n'
|
||||||
|
' * Created at: 2018-01-04T20:07:58.078+11:00\n'
|
||||||
|
' * Status: done\n'
|
||||||
|
' * User: John Doe\n'
|
||||||
|
' * Team: testing team 1\n'
|
||||||
|
' * ID: fa974ad8c1acb9e81361a051a697f9dae22908d6\n'
|
||||||
|
' * GRABBING HTTP DATA\n'
|
||||||
|
' * Created at: 2018-01-04T19:07:17.214+11:00\n'
|
||||||
|
' * Status: done\n'
|
||||||
|
' * User: John Doe\n'
|
||||||
|
' * Team: testing team 1\n'
|
||||||
|
' * ID: 72c8241d2218464433268c5abd6625ac104e3d8f\n')
|
||||||
|
|
||||||
|
def test_bot_responds_to_empty_message(self) -> None:
|
||||||
|
with self.mock_config_info({'api_key': '12345678', 'bot_info': 'team'}), \
|
||||||
|
self.mock_http_conversation('api_noop'):
|
||||||
|
self.verify_reply('',
|
||||||
|
'Sorry, I don\'t understand what your trying to say. Use `@mention help` to see my help. '
|
||||||
|
'I can\'t understand the command you sent me :confused: ')
|