diff --git a/zulip_bots/zulip_bots/bots/baremetrics/__init__.py b/zulip_bots/zulip_bots/bots/baremetrics/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/zulip_bots/zulip_bots/bots/baremetrics/assets/list-commands.png b/zulip_bots/zulip_bots/bots/baremetrics/assets/list-commands.png new file mode 100644 index 0000000..15d27e6 Binary files /dev/null and b/zulip_bots/zulip_bots/bots/baremetrics/assets/list-commands.png differ diff --git a/zulip_bots/zulip_bots/bots/baremetrics/baremetrics.conf b/zulip_bots/zulip_bots/bots/baremetrics/baremetrics.conf new file mode 100644 index 0000000..500d36e --- /dev/null +++ b/zulip_bots/zulip_bots/bots/baremetrics/baremetrics.conf @@ -0,0 +1,2 @@ +[baremetrics] +api_key = diff --git a/zulip_bots/zulip_bots/bots/baremetrics/baremetrics.py b/zulip_bots/zulip_bots/bots/baremetrics/baremetrics.py new file mode 100644 index 0000000..3627f23 --- /dev/null +++ b/zulip_bots/zulip_bots/bots/baremetrics/baremetrics.py @@ -0,0 +1,172 @@ +# See readme.md for instructions on running this code. + +from typing import Any +import requests + +class BaremetricsHandler(object): + def initialize(self, bot_handler: Any) -> None: + self.config_info = bot_handler.get_config_info('baremetrics') + self.api_key = self.config_info['api_key'] + + self.auth_header = { + 'Authorization': 'Bearer ' + self.api_key + } + + self.commands = ['help', 'list-commands', 'account-info', 'list-sources', 'list-plans ', + 'list-customers ', + 'list-subscriptions '] + + self.descriptions = ['Display bot info', 'Display the list of available commands', 'Display the account info', + 'List the sources', 'List the plans for the source', 'List the customers in the source', + 'List the subscriptions in the source'] + + def usage(self) -> str: + return ''' + This bot gives updates about customer behavior, financial performance, and analytics + for an organization using the Baremetrics Api.\n + Enter `list-commands` to show the list of available commands. + Version 1.0 + ''' + + def handle_message(self, message: Any, bot_handler: Any) -> None: + message['content'] = message['content'].strip() + + if message['content'].lower() == 'help': + bot_handler.send_reply(message, self.usage()) + return + + if message['content'].lower() == 'list-commands': + response = '**Available Commands:** \n' + for command, description in zip(self.commands, self.descriptions): + response += ' - {} : {}\n'.format(command, description) + + bot_handler.send_reply(message, response) + return + + if message['content'] == '': + bot_handler.send_reply(message, 'No Command Specified') + return + + response = self.generate_response(message['content']) + bot_handler.send_reply(message, response) + + def generate_response(self, command: str) -> str: + try: + if command.lower() == 'account-info': + return self.get_account_info() + + if command.lower() == 'list-sources': + return self.get_sources() + + part_commands = command.split() + + try: + if part_commands[0].lower() == 'list-plans': + return self.get_plans(part_commands[1]) + + if part_commands[0].lower() == 'list-customers': + return self.get_customers(part_commands[1]) + + if part_commands[0].lower() == 'list-subscriptions': + return self.get_subscriptions(part_commands[1]) + + except IndexError: + return 'Missing Params.' + except KeyError: + return 'Invalid Response From API.' + + return 'Invalid Command.' + + def get_account_info(self) -> str: + url = "https://api.baremetrics.com/v1/account" + account_response = requests.get(url, headers=self.auth_header) + + account_data = account_response.json() + account_data = account_data['account'] + + response = '**Your account information:** \n' + response += 'Id: {id}\n'.format(id=account_data['id']) + response += 'Company: {company}\n'.format(company=account_data['company']) + response += 'Default Currency: {currency_name}'.format(currency_name=account_data['default_currency']['name']) + + return response + + def get_sources(self) -> str: + url = 'https://api.baremetrics.com/v1/sources' + sources_response = requests.get(url, headers=self.auth_header) + + sources_data = sources_response.json() + sources_data = sources_data['sources'] + + response = '**Listing sources:** \n' + for index, source in enumerate(sources_data): + response += '{}.ID: {}\nProvider: {}\nProvider ID: {}\n\n'.format(index + 1, source['id'], + source['provider'], + source['provider_id']) + + return response + + def get_plans(self, source_id: str) -> str: + url = 'https://api.baremetrics.com/v1/{}/plans'.format(source_id) + plans_response = requests.get(url, headers=self.auth_header) + + plans_data = plans_response.json() + plans_data = plans_data['plans'] + + template = '{}.Name: {}\nActive: {}\nInterval: {}\nInterval Count: {}\nAmounts: \n' + response = '**Listing plans:** \n' + for index, plan in enumerate(plans_data): + response += template.format(index + 1, plan['name'], plan['active'], plan['interval'], + plan['interval_count']) + + for amount in plan['amounts']: + response += ' - {} {}\n'.format(amount['amount'], amount['currency']) + + response += '\n' + + return response + + def get_customers(self, source_id: str) -> str: + url = 'https://api.baremetrics.com/v1/{}/customers'.format(source_id) + customers_response = requests.get(url, headers=self.auth_header) + + customers_data = customers_response.json() + customers_data = customers_data['customers'] + + template = '{}.Name: {}\nDisplay Name: {}\nOID: {}\nActive: {}\nEmail: {}\nNotes: {}\nCurrent Plans: \n' + response = '**Listing customers:** \n' + for index, customer in enumerate(customers_data): + response += template.format(index + 1, customer['display_name'], customer['name'], customer['oid'], + customer['is_active'], customer['email'], customer['notes']) + + for plan in customer['current_plans']: + response += ' - {}\n'.format(plan['name']) + + response += '\n' + + return response + + def get_subscriptions(self, source_id: str) -> str: + url = 'https://api.baremetrics.com/v1/{}/subscriptions'.format(source_id) + subscriptions_response = requests.get(url, headers=self.auth_header) + + subscriptions_data = subscriptions_response.json() + subscriptions_data = subscriptions_data['subscriptions'] + + template = '{}.Customer Name: {}\nCustomer Display Name: {}\nCustomer OID: {}\nCustomer Email: {}\n' \ + 'Active: {}\nPlan Name: {}\nPlan Amounts: \n' + response = '**Listing subscriptions:** \n' + for index, subscription in enumerate(subscriptions_data): + response += template.format(index + 1, subscription['customer']['name'], + subscription['customer']['display_name'], + subscription['customer']['oid'], subscription['customer']['email'], + subscription['active'], subscription['plan']['name']) + + for amount in subscription['plan']['amounts']: + response += ' - {} {}\n'.format(amount['amount'], amount['symbol']) + + response += '\n' + + return response + +handler_class = BaremetricsHandler diff --git a/zulip_bots/zulip_bots/bots/baremetrics/doc.md b/zulip_bots/zulip_bots/bots/baremetrics/doc.md new file mode 100644 index 0000000..720ca3b --- /dev/null +++ b/zulip_bots/zulip_bots/bots/baremetrics/doc.md @@ -0,0 +1,41 @@ +# Baremetrics bot + +The Baremetrics bot is a Zulip bot that gives updates about customer behavior, financial performance, and +analytics for an organization using the [Baremetrics](https://baremetrics.com/) API. + +To use the Baremetrics bot, you can simply call it with `@` followed +by a command, like so: + +``` +@Baremetrics help +``` + +## Setup + +Before usage, you will need to configure the bot by putting the value of the `` in the config file. +To do this, follow the given steps: + +1. Login at [Baremetrics Console](https://app.baremetrics.com/settings/api). +2. Note the `Live API Key`. +3. Open up `zulip_bots/bots/baremetrics/baremetrics.conf` in an editor and + change the value of the `` attribute to the noted `Live API Key`. + +## Developer Notes + +Be sure to add the command and its description to their respective lists (named `commands` and `descriptions`) +so that it can be displayed with the other available commands using `@ list-commands`. Also modify +the `test_list_commands_command` in `test_baremetrics.py`. + +## Links + + - [Baremetrics](https://baremetrics.com/) + - [Baremetrics Developer API](https://developers.baremetrics.com/reference) + - [Baremetrics Dashboard](https://app.baremetrics.com/setup) + +## Usage + +`@Baremetrics list-commands` - This command gives a list of all available commands along with short +short descriptions. + +Example: +![](assets/list-commands.png) diff --git a/zulip_bots/zulip_bots/bots/baremetrics/fixtures/account_info.json b/zulip_bots/zulip_bots/bots/baremetrics/fixtures/account_info.json new file mode 100644 index 0000000..466f9aa --- /dev/null +++ b/zulip_bots/zulip_bots/bots/baremetrics/fixtures/account_info.json @@ -0,0 +1,59 @@ +{ + "request": { + "api_url": "https://api.baremetrics.com/v1/account", + "headers": { + "Authorization": "Bearer TEST" + } + }, + "response": { + "account": { + "id": 376418, + "default_currency": { + "id": "usd", + "alternate_symbols": [ + "US$" + ], + "decimal_mark": ".", + "disambiguate_symbol": "US$", + "html_entity": "$", + "iso_code": "USD", + "iso_numeric": "840", + "name": "United States Dollar", + "priority": 1, + "smallest_denomination": 1, + "subunit": "Cent", + "subunit_to_unit": 100, + "symbol": "$", + "symbol_first": true, + "thousands_separator": "," + }, + "company": "NA", + "created_at": 1514301115, + "stack": "seg" + } + }, + "response-headers": { + "X-TokenExpires": "0", + "Server": "cloudflare-nginx", + "X-RateLimit-Remaining": "3593", + "X-Powered-By": "Phusion Passenger 5.0.30", + "Set-Cookie": "__cfduid=dd26464e3e6779b6a05b27d2681aea0851514374567; expires=Thu, 27-Dec-18 11:36:07 GMT; path=/; domain=.baremetrics.com; HttpOnly; Secure, LSW_WEB=\"LSW_WEB1\"; path=/", + "X-Runtime": "0.031730", + "Access-Control-Allow-Credentials": "false", + "Access-Control-Allow-Methods": "GET, OPTIONS, POST, PUT, DELETE", + "Content-Encoding": "gzip", + "Connection": "keep-alive", + "ETag": "W/\"110de39b319ed195bf1414ac4f3c9a01\"", + "Date": "Wed, 27 Dec 2017 11:36:08 GMT", + "X-RateLimit-Limit": "3600", + "X-Version": "721", + "X-Commit": "2dcec7190a2bd01c8d3d8527d386486c4bb5474c", + "Status": "200 OK", + "Content-Type": "application/json; charset=utf-8", + "Transfer-Encoding": "chunked", + "Access-Control-Allow-Headers": "Authorization,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type", + "Cache-Control": "max-age=0, private, must-revalidate", + "X-Request-Id": "0b292131-81bb-4df2-bdb9-089a50b17e63", + "CF-RAY": "3d3bfaf6feee2eff-DEL" + } +} diff --git a/zulip_bots/zulip_bots/bots/baremetrics/fixtures/list_customers.json b/zulip_bots/zulip_bots/bots/baremetrics/fixtures/list_customers.json new file mode 100644 index 0000000..ef9989d --- /dev/null +++ b/zulip_bots/zulip_bots/bots/baremetrics/fixtures/list_customers.json @@ -0,0 +1,43 @@ +{ + "request": { + "api_url": "https://api.baremetrics.com/v1/TEST/customers", + "headers": { + "Authorization": "Bearer TEST" + } + }, + "response": { + "customers": [], + "meta": { + "pagination": { + "has_more": false, + "page": 0, + "per_page": 30, + "total_count": 0 + } + } + }, + "response-headers": { + "X-TokenExpires": "0", + "Server": "cloudflare-nginx", + "X-RateLimit-Remaining": "3593", + "X-Powered-By": "Phusion Passenger 5.0.30", + "Set-Cookie": "__cfduid=dd26464e3e6779b6a05b27d2681aea0851514374567; expires=Thu, 27-Dec-18 11:36:07 GMT; path=/; domain=.baremetrics.com; HttpOnly; Secure, LSW_WEB=\"LSW_WEB1\"; path=/", + "X-Runtime": "0.031730", + "Access-Control-Allow-Credentials": "false", + "Access-Control-Allow-Methods": "GET, OPTIONS, POST, PUT, DELETE", + "Content-Encoding": "gzip", + "Connection": "keep-alive", + "ETag": "W/\"110de39b319ed195bf1414ac4f3c9a01\"", + "Date": "Wed, 27 Dec 2017 11:36:08 GMT", + "X-RateLimit-Limit": "3600", + "X-Version": "721", + "X-Commit": "2dcec7190a2bd01c8d3d8527d386486c4bb5474c", + "Status": "200 OK", + "Content-Type": "application/json; charset=utf-8", + "Transfer-Encoding": "chunked", + "Access-Control-Allow-Headers": "Authorization,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type", + "Cache-Control": "max-age=0, private, must-revalidate", + "X-Request-Id": "0b292131-81bb-4df2-bdb9-089a50b17e63", + "CF-RAY": "3d3bfaf6feee2eff-DEL" + } +} diff --git a/zulip_bots/zulip_bots/bots/baremetrics/fixtures/list_plans.json b/zulip_bots/zulip_bots/bots/baremetrics/fixtures/list_plans.json new file mode 100644 index 0000000..68bc536 --- /dev/null +++ b/zulip_bots/zulip_bots/bots/baremetrics/fixtures/list_plans.json @@ -0,0 +1,42 @@ +{ + "request": { + "api_url": "https://api.baremetrics.com/v1/TEST/plans", + "headers": { + "Authorization": "Bearer TEST" + } + }, + "response": { + "plans": [], + "meta": { + "pagination": { + "has_more": false, + "page": 0, + "per_page": 30 + } + } + }, + "response-headers": { + "X-TokenExpires": "0", + "Server": "cloudflare-nginx", + "X-RateLimit-Remaining": "3593", + "X-Powered-By": "Phusion Passenger 5.0.30", + "Set-Cookie": "__cfduid=dd26464e3e6779b6a05b27d2681aea0851514374567; expires=Thu, 27-Dec-18 11:36:07 GMT; path=/; domain=.baremetrics.com; HttpOnly; Secure, LSW_WEB=\"LSW_WEB1\"; path=/", + "X-Runtime": "0.031730", + "Access-Control-Allow-Credentials": "false", + "Access-Control-Allow-Methods": "GET, OPTIONS, POST, PUT, DELETE", + "Content-Encoding": "gzip", + "Connection": "keep-alive", + "ETag": "W/\"110de39b319ed195bf1414ac4f3c9a01\"", + "Date": "Wed, 27 Dec 2017 11:36:08 GMT", + "X-RateLimit-Limit": "3600", + "X-Version": "721", + "X-Commit": "2dcec7190a2bd01c8d3d8527d386486c4bb5474c", + "Status": "200 OK", + "Content-Type": "application/json; charset=utf-8", + "Transfer-Encoding": "chunked", + "Access-Control-Allow-Headers": "Authorization,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type", + "Cache-Control": "max-age=0, private, must-revalidate", + "X-Request-Id": "0b292131-81bb-4df2-bdb9-089a50b17e63", + "CF-RAY": "3d3bfaf6feee2eff-DEL" + } +} diff --git a/zulip_bots/zulip_bots/bots/baremetrics/fixtures/list_sources.json b/zulip_bots/zulip_bots/bots/baremetrics/fixtures/list_sources.json new file mode 100644 index 0000000..77b33fb --- /dev/null +++ b/zulip_bots/zulip_bots/bots/baremetrics/fixtures/list_sources.json @@ -0,0 +1,48 @@ +{ + "request": { + "api_url": "https://api.baremetrics.com/v1/sources", + "headers": { + "Authorization": "Bearer TEST" + } + }, + "response": { + "sources": [ + { + "id": "5f7QC5NC0Ywgcu", + "provider": "baremetrics", + "provider_id": null + } + ], + "meta": { + "pagination": { + "has_more": false, + "page": 0, + "per_page": 30 + } + } + }, + "response-headers": { + "X-TokenExpires": "0", + "Server": "cloudflare-nginx", + "X-RateLimit-Remaining": "3593", + "X-Powered-By": "Phusion Passenger 5.0.30", + "Set-Cookie": "__cfduid=dd26464e3e6779b6a05b27d2681aea0851514374567; expires=Thu, 27-Dec-18 11:36:07 GMT; path=/; domain=.baremetrics.com; HttpOnly; Secure, LSW_WEB=\"LSW_WEB1\"; path=/", + "X-Runtime": "0.031730", + "Access-Control-Allow-Credentials": "false", + "Access-Control-Allow-Methods": "GET, OPTIONS, POST, PUT, DELETE", + "Content-Encoding": "gzip", + "Connection": "keep-alive", + "ETag": "W/\"110de39b319ed195bf1414ac4f3c9a01\"", + "Date": "Wed, 27 Dec 2017 11:36:08 GMT", + "X-RateLimit-Limit": "3600", + "X-Version": "721", + "X-Commit": "2dcec7190a2bd01c8d3d8527d386486c4bb5474c", + "Status": "200 OK", + "Content-Type": "application/json; charset=utf-8", + "Transfer-Encoding": "chunked", + "Access-Control-Allow-Headers": "Authorization,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type", + "Cache-Control": "max-age=0, private, must-revalidate", + "X-Request-Id": "0b292131-81bb-4df2-bdb9-089a50b17e63", + "CF-RAY": "3d3bfaf6feee2eff-DEL" + } +} diff --git a/zulip_bots/zulip_bots/bots/baremetrics/fixtures/list_subscriptions.json b/zulip_bots/zulip_bots/bots/baremetrics/fixtures/list_subscriptions.json new file mode 100644 index 0000000..0cd1e67 --- /dev/null +++ b/zulip_bots/zulip_bots/bots/baremetrics/fixtures/list_subscriptions.json @@ -0,0 +1,42 @@ +{ + "request": { + "api_url": "https://api.baremetrics.com/v1/TEST/subscriptions", + "headers": { + "Authorization": "Bearer TEST" + } + }, + "response": { + "subscriptions": [], + "meta": { + "pagination": { + "has_more": false, + "page": 0, + "per_page": 30 + } + } + }, + "response-headers": { + "X-TokenExpires": "0", + "Server": "cloudflare-nginx", + "X-RateLimit-Remaining": "3593", + "X-Powered-By": "Phusion Passenger 5.0.30", + "Set-Cookie": "__cfduid=dd26464e3e6779b6a05b27d2681aea0851514374567; expires=Thu, 27-Dec-18 11:36:07 GMT; path=/; domain=.baremetrics.com; HttpOnly; Secure, LSW_WEB=\"LSW_WEB1\"; path=/", + "X-Runtime": "0.031730", + "Access-Control-Allow-Credentials": "false", + "Access-Control-Allow-Methods": "GET, OPTIONS, POST, PUT, DELETE", + "Content-Encoding": "gzip", + "Connection": "keep-alive", + "ETag": "W/\"110de39b319ed195bf1414ac4f3c9a01\"", + "Date": "Wed, 27 Dec 2017 11:36:08 GMT", + "X-RateLimit-Limit": "3600", + "X-Version": "721", + "X-Commit": "2dcec7190a2bd01c8d3d8527d386486c4bb5474c", + "Status": "200 OK", + "Content-Type": "application/json; charset=utf-8", + "Transfer-Encoding": "chunked", + "Access-Control-Allow-Headers": "Authorization,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type", + "Cache-Control": "max-age=0, private, must-revalidate", + "X-Request-Id": "0b292131-81bb-4df2-bdb9-089a50b17e63", + "CF-RAY": "3d3bfaf6feee2eff-DEL" + } +} diff --git a/zulip_bots/zulip_bots/bots/baremetrics/test_baremetrics.py b/zulip_bots/zulip_bots/bots/baremetrics/test_baremetrics.py new file mode 100644 index 0000000..b71fa55 --- /dev/null +++ b/zulip_bots/zulip_bots/bots/baremetrics/test_baremetrics.py @@ -0,0 +1,53 @@ +from zulip_bots.test_lib import BotTestCase + +class TestBaremetricsBot(BotTestCase): + bot_name = "baremetrics" + + def test_bot_responds_to_empty_message(self) -> None: + with self.mock_config_info({'api_key': 'TEST'}): + self.verify_reply('', 'No Command Specified') + + def test_help_query(self) -> None: + with self.mock_config_info({'api_key': 'TEST'}): + self.verify_reply('help', ''' + This bot gives updates about customer behavior, financial performance, and analytics + for an organization using the Baremetrics Api.\n + Enter `list-commands` to show the list of available commands. + Version 1.0 + ''') + + def test_list_commands_command(self) -> None: + with self.mock_config_info({'api_key': 'TEST'}): + self.verify_reply('list-commands', '**Available Commands:** \n - help : Display bot info\n - list-commands ' + ': Display the list of available commands\n - account-info : Display ' + 'the account info\n - list-sources : List the sources\n - list-plans ' + ' : List the plans for the source\n - list-customers ' + ' : List the customers in the source\n - list-subscriptions ' + ' : List the subscriptions in the source\n') + + def test_account_info_command(self) -> None: + with self.mock_config_info({'api_key': 'TEST'}): + with self.mock_http_conversation('account_info'): + self.verify_reply('account-info', '**Your account information:** \nId: 376418\nCompany: NA\nDefault ' + 'Currency: United States Dollar') + + def test_list_sources_command(self) -> None: + with self.mock_config_info({'api_key': 'TEST'}): + with self.mock_http_conversation('list_sources'): + self.verify_reply('list-sources', '**Listing sources:** \n1.ID: 5f7QC5NC0Ywgcu\nProvider: ' + 'baremetrics\nProvider ID: None\n\n') + + def test_list_plans_command(self) -> None: + with self.mock_config_info({'api_key': 'TEST'}): + with self.mock_http_conversation('list_plans'): + self.verify_reply('list-plans TEST', '**Listing plans:** \n') + + def test_list_customers_command(self) -> None: + with self.mock_config_info({'api_key': 'TEST'}): + with self.mock_http_conversation('list_customers'): + self.verify_reply('list-customers TEST', '**Listing customers:** \n') + + def test_list_subscriptions_command(self) -> None: + with self.mock_config_info({'api_key': 'TEST'}): + with self.mock_http_conversation('list_subscriptions'): + self.verify_reply('list-subscriptions TEST', '**Listing subscriptions:** \n')