Skip to content

New Zulip bot named bugzilla #619

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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,7 @@ botserverrc
# Pycharm
\.DS_Store
\.idea/

# VS Code
.vscode/settings.json
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 we can ignore .vscode.


Empty file.
3 changes: 3 additions & 0 deletions zulip_bots/zulip_bots/bots/bugzilla/bugzilla.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[bugzilla]
site = https://bugs.site.net
api_key = xxxx
92 changes: 92 additions & 0 deletions zulip_bots/zulip_bots/bots/bugzilla/bugzilla.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import re
import requests
from typing import Any, Dict

TOPIC_REGEX = re.compile('^Bug (?P<bug_number>.+)$')

HELP_REGEX = re.compile('help$')

HELP_RESPONSE = '''
**help**

`help` returns this short help

**comment**

With no argument, by default, a new comment is added to the bug that is associated to the topic.
For example, on topic Bug 123,

you:

> @**Bugzilla** A new comment

Then `A new comment` is added to bug 123
'''


class BugzillaHandler(object):
'''
A docstring documenting this bot.
'''

def usage(self):
return '''
Bugzilla Bot uses the Bugzilla REST API v1 to interact with Bugzilla. In order to use
Bugzilla Bot, `bugzilla.conf` must be set up. See `doc.md` for more details.
'''

def initialize(self, bot_handler: Any) -> None:
config = bot_handler.get_config_info('bugzilla')

site = config.get('site')
api_key = config.get('api_key')
if not site:
raise KeyError('No `site` was specified')
if not api_key:
raise KeyError('No `api_key` was specified')

self.site = site
self.api_key = api_key

def handle_message(self, message: Dict[str, str], bot_handler: Any) -> None:
content = message.get('content')
topic = message.get('subject')

if HELP_REGEX.match(content):
self.handle_help(message, bot_handler)
return None

try:
bug_number = self.extract_bug_number(topic)
except ValueError:
bot_handler.send_reply(message, 'Unsupported topic: ' + topic)
return None

comment = content
self.handle_comment(bug_number, comment, message, bot_handler)

def handle_help(self, message: Dict[str, str], bot_handler: Any) -> None:
bot_handler.send_reply(message, HELP_RESPONSE)

def handle_comment(self, bug_number: str, comment: str, message: Dict[str, str], bot_handler: Any) -> None:
url = '{}/rest/bug/{}/comment'.format(self.site, bug_number)
requests.post(url,
json=self.make_comment_json(comment))

def make_comment_json(self, comment: str) -> Any:
json = {
'api_key': self.api_key,
'comment': comment
}
return json

def extract_bug_number(self, topic: str) -> Any:
topic_match = TOPIC_REGEX.match(topic)

if not topic_match:
raise ValueError
else:
return topic_match.group('bug_number')


handler_class = BugzillaHandler
37 changes: 37 additions & 0 deletions zulip_bots/zulip_bots/bots/bugzilla/doc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Bugzilla bot

This bot allows to update directly Bugzilla from Zulip

## Setup

To use Bugzilla Bot, first set up `bugzilla.conf`. `bugzilla.conf` takes 2 options:
- site (the site like `https://bugs.xxx.net` that includes both the protocol and the domain)
- api_key (a Bugzilla API key)

Example:
```
[bugzilla]
site = https://bugs.site.net
api_key = xxxx
```


## Usage

Run this bot as described
[here](https://zulipchat.com/api/running-bots#running-a-bot).

Use this bot with the following command

`@mentioned-bot <action>` in a topic that is named `Bug 123` where 123 is the bug number

### comment

With no argument, by default, a new comment is added to the bug that is associated to the topic.
For example, on topic Bug 123,

you:

> @**Bugzilla** A new comment

Then `A new comment` is added to bug 123
17 changes: 17 additions & 0 deletions zulip_bots/zulip_bots/bots/bugzilla/fixtures/test_comment.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"request": {
"api_url": "https://bugs.net/rest/bug/123/comment",
"method": "POST",
"json": {
"api_key": "kkk",
"comment": "a comment"
}
},
"response": {
"id": 124
},
"response-headers": {
"status": 200,
"content-type": "application/json; charset=utf-8"
}
}
94 changes: 94 additions & 0 deletions zulip_bots/zulip_bots/bots/bugzilla/test_bugzilla.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
from typing import Any, Dict
from zulip_bots.test_lib import BotTestCase, DefaultTests


class TestBugzillaBot(BotTestCase, DefaultTests):
bot_name = 'bugzilla'

MOCK_CONFIG_INFO = {
'site': 'https://bugs.net',
'api_key': 'kkk'
}

MOCK_HELP_RESPONSE = '''
**help**

`help` returns this short help

**comment**

With no argument, by default, a new comment is added to the bug that is associated to the topic.
For example, on topic Bug 123,

you:

> @**Bugzilla** A new comment

Then `A new comment` is added to bug 123
'''

def make_request_message(self, content: str) -> Dict[str, Any]:
message = super().make_request_message(content)
message['subject'] = "Bug 123"
return message

def handle_message_only(self, request: str) -> Dict[str, Any]:
bot, bot_handler = self._get_handlers()
message = self.make_request_message(request)
bot_handler.reset_transcript()
bot.handle_message(message, bot_handler)

def test_bot_responds_to_empty_message(self) -> None:
pass

def _test_invalid_config(self, invalid_config, error_message) -> None:
with self.mock_config_info(invalid_config), \
self.assertRaisesRegexp(KeyError, error_message):
bot, bot_handler = self._get_handlers()

def test_config_without_site(self) -> None:
config_without_site = {
'api_key': 'kkk',
}
self._test_invalid_config(config_without_site,
'No `site` was specified')

def test_config_without_api_key(self) -> None:
config_without_api_key = {
'site': 'https://bugs.xx',
}
self._test_invalid_config(config_without_api_key,
'No `api_key` was specified')

def test_comment(self) -> None:
with self.mock_config_info(self.MOCK_CONFIG_INFO), \
self.mock_http_conversation('test_comment'):
self.handle_message_only('a comment')

def test_help(self) -> None:
with self.mock_config_info(self.MOCK_CONFIG_INFO):
self.verify_reply('help', self.MOCK_HELP_RESPONSE)


class TestBugzillaBotWrongTopic(BotTestCase, DefaultTests):
bot_name = 'bugzilla'

MOCK_CONFIG_INFO = {
'site': 'https://bugs.net',
'api_key': 'kkk'
}

MOCK_COMMENT_INVALID_TOPIC_RESPONSE = 'Unsupported topic: kqatm2'

def make_request_message(self, content: str) -> Dict[str, Any]:
message = super().make_request_message(content)
message['subject'] = "kqatm2"
return message

def test_bot_responds_to_empty_message(self) -> None:
pass

def test_no_bug_number(self) -> None:
with self.mock_config_info(self.MOCK_CONFIG_INFO):
self.verify_reply(
'a comment', self.MOCK_COMMENT_INVALID_TOPIC_RESPONSE)