interactive bots: Create Salesforce bot.
This commit is contained in:
		
							parent
							
								
									41b065eb76
								
							
						
					
					
						commit
						08bfe9d8c7
					
				
					 16 changed files with 593 additions and 2 deletions
				
			
		|  | @ -78,7 +78,9 @@ force_include = [ | |||
|     "zulip_bots/zulip_bots/bots/mention/mention.py", | ||||
|     "zulip_bots/zulip_bots/bots/mention/test_mention.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/test_salesforce.py" | ||||
| ] | ||||
| 
 | ||||
| parser = argparse.ArgumentParser(description="Run mypy on files tracked by git.") | ||||
|  |  | |||
|  | @ -55,7 +55,8 @@ setuptools_info = dict( | |||
|         'requests',  # for bots/link_shortener and bots/jira | ||||
|         'python-chess[engine,gaviota]',  # for bots/chess | ||||
|         'wit',  # for bots/witai | ||||
|         'apiai'  # for bots/dialogflow | ||||
|         'apiai',  # for bots/dialogflow | ||||
|         'simple_salesforce'  # for bots/salesforce | ||||
|     ], | ||||
| ) | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										0
									
								
								zulip_bots/zulip_bots/bots/salesforce/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								zulip_bots/zulip_bots/bots/salesforce/__init__.py
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 16 KiB | 
							
								
								
									
										
											BIN
										
									
								
								zulip_bots/zulip_bots/bots/salesforce/assets/query_example.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								zulip_bots/zulip_bots/bots/salesforce/assets/query_example.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 32 KiB | 
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 15 KiB | 
							
								
								
									
										80
									
								
								zulip_bots/zulip_bots/bots/salesforce/doc.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								zulip_bots/zulip_bots/bots/salesforce/doc.md
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,80 @@ | |||
| # Salesforce bot | ||||
| 
 | ||||
| The Salesforce bot can get records from your Salesforce database. | ||||
| It can also show details about any Salesforce links that you post. | ||||
| 
 | ||||
| ## Setup | ||||
| 
 | ||||
| 1. Create a user in Salesforce that the bot can use to access Salesforce. | ||||
| Make sure it has the appropriate permissions to access records. | ||||
| 2. In `salesforce.conf` paste the Salesforce `username`, `password` and | ||||
| `security_token`. | ||||
| 3. Run the bot as explained [here](https://zulipchat.com/api/running-bots#running-a-bot) | ||||
| 
 | ||||
| ## Examples | ||||
| 
 | ||||
| ### Standard query | ||||
|  | ||||
| 
 | ||||
| ### Custom query | ||||
|  | ||||
| 
 | ||||
| ### Link details | ||||
|  | ||||
| 
 | ||||
| ## Optional Configuration (Advanced) | ||||
| 
 | ||||
| The bot has been designed to be able to configure custom commands and objects. | ||||
| 
 | ||||
| If you wanted to find a custom object type, or an object type not included with the bot, | ||||
| like `Event`, you can add these by adding to the Commands and Object Types in `utils.py`. | ||||
| 
 | ||||
| A Command is a phrase that the User asks the bot. For example `find contact bob`. To make a Command, | ||||
| the corresponding object type must be made. | ||||
| 
 | ||||
| Object types are Salesforce objects, like `Event`, and are used to tell the bot which fields of the object the bot | ||||
| should ask for and display. | ||||
| 
 | ||||
| To show details about a link posted, only the Object Type for the object needs to be present. | ||||
| 
 | ||||
| Please read the | ||||
| [SOQL reference](https://goo.gl/6VwBV3) | ||||
| to make custom queries, and the [simple_salesforce documentation](https://pypi.python.org/pypi/simple-salesforce) | ||||
| to make custom callbacks. | ||||
| 
 | ||||
| ### Commands | ||||
| 
 | ||||
| For example: "find contact tim" | ||||
| 
 | ||||
| In `utils.py`, the commands are stored in the list `commands`. | ||||
| 
 | ||||
| Parameter | Required? | Type | Description | Default | ||||
| --------- | --------- | ---- | ----------- | ------- | ||||
| commands | [x] | list[str] | What the user should start their command with | `None` | ||||
| object | [x] | str | The Salesforce object type in `object_types` | `None` | ||||
| query | [ ] | str | The SOQL query to access this object* | `'SELECT {} FROM {} WHERE Name LIKE %\'{}\'% LIMIT {}'` | ||||
| description | [x] | str | What does the command do? | `None` | ||||
| template | [x] | str | Example of the command | `None` | ||||
| rank_output | [ ] | boolean | Should the output be ranked? (1., 2., 3. etc.) | `False` | ||||
| force_keys | [ ] | list[str] | Values which should always be shown in the output | `[]` | ||||
| callback | [ ] | callable** | Custom handling behaviour | `None` | ||||
| 
 | ||||
| **Note**: *`query` must have `LIMIT {}` at the end, and the 4 parameters are `fields`, `table` (from `object_types`), | ||||
| `args` (the search term), `limit` (the maximum number of terms) | ||||
| 
 | ||||
| **`callback` must be a function which accepts `args: str`(arguments passed in by the user, including search term), | ||||
| `sf: simple_salesforce.api.Salesforce` (the Salesforce handler object, `self.sf`), `command: Dict[str, Any]` | ||||
| (the command used from `commands`) | ||||
| 
 | ||||
| ### Object Types | ||||
| In `utils.py` the object types are stored in the dictionary `object_types`. | ||||
| 
 | ||||
| The name of each object type corresponds to the `object` referenced in `commands`. | ||||
| 
 | ||||
| Parameter | Required? | Type | Description | ||||
| --------- | --------- | ---- | ----------- | ||||
| fields* | [x] | str | The Salesforce fields to fetch from the database. | ||||
| name | [x] | str | The API name of the object**. | ||||
| 
 | ||||
| **Note**: * This must contain Name and Id, however Id is not displayed. | ||||
| ** Found in the salesforce object manager. | ||||
|  | @ -0,0 +1,26 @@ | |||
| { | ||||
|     "response": { | ||||
|         "totalSize": 2, | ||||
|         "done": true, | ||||
|         "records": [ | ||||
|             { | ||||
|                 "attributes": { | ||||
|                     "type": "Contact", | ||||
|                     "url": "" | ||||
|                 }, | ||||
|                 "Id": "foo_id", | ||||
|                 "Name": "foo", | ||||
|                 "Phone": "020 1234 5678" | ||||
|             }, | ||||
|             { | ||||
|                 "attributes": { | ||||
|                     "type": "Contact", | ||||
|                     "url": "" | ||||
|                 }, | ||||
|                 "Id": "bar_id", | ||||
|                 "Name": "bar", | ||||
|                 "Phone": "020 5678 1234" | ||||
|             } | ||||
|         ] | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,8 @@ | |||
| { | ||||
|     "response": { | ||||
|         "totalSize": 0, | ||||
|         "done": true, | ||||
|         "records": [ | ||||
|         ] | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,17 @@ | |||
| { | ||||
|     "response": { | ||||
|         "totalSize": 1, | ||||
|         "done": true, | ||||
|         "records": [ | ||||
|             { | ||||
|                 "attributes": { | ||||
|                     "type": "Contact", | ||||
|                     "url": "" | ||||
|                 }, | ||||
|                 "Id": "foo_id", | ||||
|                 "Name": "foo", | ||||
|                 "Phone": "020 1234 5678" | ||||
|             } | ||||
|         ] | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,26 @@ | |||
| { | ||||
|     "response": { | ||||
|         "totalSize": 2, | ||||
|         "done": true, | ||||
|         "records": [ | ||||
|             { | ||||
|                 "attributes": { | ||||
|                     "type": "Opportunity", | ||||
|                     "url": "" | ||||
|                 }, | ||||
|                 "Id": "foo_id", | ||||
|                 "Name": "foo", | ||||
|                 "Amount": 2 | ||||
|             }, | ||||
|             { | ||||
|                 "attributes": { | ||||
|                     "type": "Opportunity", | ||||
|                     "url": "" | ||||
|                 }, | ||||
|                 "Id": "bar_id", | ||||
|                 "Name": "bar", | ||||
|                 "Amount": 1 | ||||
|             } | ||||
|         ] | ||||
|     } | ||||
| } | ||||
							
								
								
									
										1
									
								
								zulip_bots/zulip_bots/bots/salesforce/requirements.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								zulip_bots/zulip_bots/bots/salesforce/requirements.txt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | |||
| simple_salesforce | ||||
							
								
								
									
										4
									
								
								zulip_bots/zulip_bots/bots/salesforce/salesforce.conf
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								zulip_bots/zulip_bots/bots/salesforce/salesforce.conf
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,4 @@ | |||
| [salesforce] | ||||
| username=foo | ||||
| security_token=bar | ||||
| password=baz | ||||
							
								
								
									
										178
									
								
								zulip_bots/zulip_bots/bots/salesforce/salesforce.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								zulip_bots/zulip_bots/bots/salesforce/salesforce.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,178 @@ | |||
| # See readme.md for instructions on running this code. | ||||
| 
 | ||||
| from typing import Any | ||||
| import simple_salesforce | ||||
| from typing import Dict, Any, List | ||||
| import getpass | ||||
| import re | ||||
| import logging | ||||
| import json | ||||
| from zulip_bots.bots.salesforce.utils import * | ||||
| 
 | ||||
| base_help_text = '''Salesforce bot | ||||
| This bot can do simple salesforce query requests | ||||
| **All commands must be @-mentioned to the bot.** | ||||
| Commands: | ||||
| {} | ||||
| Arguments: | ||||
| **-limit <num>**: the maximum number of entries sent (default: 5) | ||||
| **-show**: show all the properties of each entry (default: false) | ||||
| 
 | ||||
| This bot can also show details about any Salesforce links sent to it. | ||||
| 
 | ||||
| Supported Object types: | ||||
| These are the types of Salesforce object supported by this bot. | ||||
| The bot cannot show the details of any other object types. | ||||
| {}''' | ||||
| 
 | ||||
| login_url = 'https://login.salesforce.com/' | ||||
| 
 | ||||
| 
 | ||||
| def get_help_text() -> str: | ||||
|     command_text = '' | ||||
|     for command in commands: | ||||
|         if 'template' in command.keys() and 'description' in command.keys(): | ||||
|             command_text += '**{}**: {}\n'.format('{} [arguments]'.format( | ||||
|                 command['template']), command['description']) | ||||
|     object_type_text = '' | ||||
|     for object_type in object_types.values(): | ||||
|         object_type_text += '{}\n'.format(object_type['table']) | ||||
|     return base_help_text.format(command_text, object_type_text) | ||||
| 
 | ||||
| 
 | ||||
| def format_result( | ||||
|         result: Dict[str, Any], | ||||
|         exclude_keys: List[str]=[], | ||||
|         force_keys: List[str]=[], | ||||
|         rank_output: bool=False, | ||||
|         show_all_keys: bool=False | ||||
| ) -> str: | ||||
|     exclude_keys += ['Name', 'attributes', 'Id'] | ||||
|     output = '' | ||||
|     if result['totalSize'] == 0: | ||||
|         return 'No records found.' | ||||
|     if result['totalSize'] == 1: | ||||
|         record = result['records'][0] | ||||
|         output += '**[{}]({}{})**\n'.format(record['Name'], | ||||
|                                             login_url, record['Id']) | ||||
|         for key, value in record.items(): | ||||
|             if key not in exclude_keys: | ||||
|                 output += '>**{}**: {}\n'.format(key, value) | ||||
|     else: | ||||
|         for i, record in enumerate(result['records']): | ||||
|             if rank_output: | ||||
|                 output += '{}) '.format(i + 1) | ||||
|             output += '**[{}]({}{})**\n'.format(record['Name'], | ||||
|                                                 login_url, record['Id']) | ||||
|             added_keys = False | ||||
|             for key, value in record.items(): | ||||
|                 if key in force_keys or (show_all_keys and key not in exclude_keys): | ||||
|                     added_keys = True | ||||
|                     output += '>**{}**: {}\n'.format(key, value) | ||||
|             if added_keys: | ||||
|                 output += '\n' | ||||
|     return output | ||||
| 
 | ||||
| 
 | ||||
| def query_salesforce(arg: str, sf: Any, command: Dict[str, Any]) -> str: | ||||
|     arg = arg.strip() | ||||
|     qarg = arg.split(' -', 1)[0] | ||||
|     split_args = []  # type: List[str] | ||||
|     raw_arg = '' | ||||
|     if len(arg.split(' -', 1)) > 1: | ||||
|         raw_arg = ' -' + arg.split(' -', 1)[1] | ||||
|         split_args = raw_arg.split(' -') | ||||
|     limit_num = 5 | ||||
|     re_limit = re.compile('-limit \d+') | ||||
|     limit = re_limit.search(raw_arg) | ||||
|     if limit: | ||||
|         limit_num = int(limit.group().rsplit(' ', 1)[1]) | ||||
|         logging.info('Searching with limit {}'.format(limit_num)) | ||||
|     query = default_query | ||||
|     if 'query' in command.keys(): | ||||
|         query = command['query'] | ||||
|     object_type = object_types[command['object']] | ||||
|     res = sf.query(query.format( | ||||
|         object_type['fields'], object_type['table'], qarg, limit_num)) | ||||
|     exclude_keys = []  # type: List[str] | ||||
|     if 'exclude_keys' in command.keys(): | ||||
|         exclude_keys = command['exclude_keys'] | ||||
|     force_keys = []  # type: List[str] | ||||
|     if 'force_keys' in command.keys(): | ||||
|         force_keys = command['force_keys'] | ||||
|     rank_output = False | ||||
|     if 'rank_output' in command.keys(): | ||||
|         rank_output = command['rank_output'] | ||||
|     show_all_keys = 'show' in split_args | ||||
|     if 'show_all_keys' in command.keys(): | ||||
|         show_all_keys = command['show_all_keys'] or 'show' in split_args | ||||
|     return format_result(res, exclude_keys=exclude_keys, force_keys=force_keys, rank_output=rank_output, show_all_keys=show_all_keys) | ||||
| 
 | ||||
| 
 | ||||
| def get_salesforce_link_details(link: str, sf: Any) -> str: | ||||
|     re_id = re.compile('/[A-Za-z0-9]{18}') | ||||
|     re_id_res = re_id.search(link) | ||||
|     if re_id_res is None: | ||||
|         return 'Invalid salesforce link' | ||||
|     id = re_id_res.group().strip('/') | ||||
|     for object_type in object_types.values(): | ||||
|         res = sf.query(link_query.format( | ||||
|             object_type['fields'], object_type['table'], id)) | ||||
|         if res['totalSize'] == 1: | ||||
|             return format_result(res) | ||||
|     return 'No object found. Make sure it is of the supported types. Type `help` for more info.' | ||||
| 
 | ||||
| 
 | ||||
| class SalesforceHandler(object): | ||||
|     def usage(self) -> str: | ||||
|         return ''' | ||||
|         This is a Salesforce bot, which can search for Contacts, | ||||
|         Accounts and Opportunities. And can be configured for any | ||||
|         other object types. | ||||
| 
 | ||||
|         It will also show details of any Salesforce links posted. | ||||
| 
 | ||||
|         @-mention the bot with 'help' to see available commands. | ||||
|         ''' | ||||
| 
 | ||||
|     def get_salesforce_response(self, content: str) -> str: | ||||
|         content = content.strip() | ||||
|         if content is None or content == 'help': | ||||
|             return get_help_text() | ||||
|         if content.startswith('http') and 'force' in content: | ||||
|             return get_salesforce_link_details(content, self.sf) | ||||
|         for command in commands: | ||||
|             for command_keyword in command['commands']: | ||||
|                 if content.startswith(command_keyword): | ||||
|                     args = content.replace(command_keyword, '').strip() | ||||
|                     if args is not None and args != '': | ||||
|                         if 'callback' in command.keys(): | ||||
|                             return command['callback'](args, self.sf, command) | ||||
|                         else: | ||||
|                             return query_salesforce(args, self.sf, command) | ||||
|                     else: | ||||
|                         return 'Usage: {} [arguments]'.format(command['template']) | ||||
|         return get_help_text() | ||||
| 
 | ||||
|     def initialize(self, bot_handler: Any) -> None: | ||||
|         self.config_info = bot_handler.get_config_info('salesforce') | ||||
|         try: | ||||
|             self.sf = simple_salesforce.Salesforce( | ||||
|                 username=self.config_info['username'], | ||||
|                 password=self.config_info['password'], | ||||
|                 security_token=self.config_info['security_token'] | ||||
|             ) | ||||
|         except simple_salesforce.exceptions.SalesforceAuthenticationFailed as err: | ||||
|             logging.error( | ||||
|                 'Failed to log in to Salesforce. {} {}'.format(err.code, err.message)) | ||||
|             quit() | ||||
| 
 | ||||
|     def handle_message(self, message: Any, bot_handler: Any) -> None: | ||||
|         try: | ||||
|             bot_response = self.get_salesforce_response(message['content']) | ||||
|             bot_handler.send_reply(message, bot_response) | ||||
|         except Exception as e: | ||||
|             bot_handler.send_reply('Error. {}.'.format(e), bot_response) | ||||
| 
 | ||||
| 
 | ||||
| handler_class = SalesforceHandler | ||||
							
								
								
									
										201
									
								
								zulip_bots/zulip_bots/bots/salesforce/test_salesforce.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								zulip_bots/zulip_bots/bots/salesforce/test_salesforce.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,201 @@ | |||
| from zulip_bots.test_lib import BotTestCase, read_bot_fixture_data | ||||
| import simple_salesforce | ||||
| from simple_salesforce.exceptions import SalesforceAuthenticationFailed | ||||
| from contextlib import contextmanager | ||||
| from unittest.mock import patch | ||||
| from typing import Any, Dict | ||||
| import logging | ||||
| 
 | ||||
| 
 | ||||
| @contextmanager | ||||
| def mock_salesforce_query(test_name: str, bot_name: str) -> Any: | ||||
|     response_data = read_bot_fixture_data(bot_name, test_name) | ||||
|     sf_response = response_data.get('response') | ||||
| 
 | ||||
|     with patch('simple_salesforce.api.Salesforce.query') as mock_query: | ||||
|         mock_query.return_value = sf_response | ||||
|         yield | ||||
| 
 | ||||
| 
 | ||||
| @contextmanager | ||||
| def mock_salesforce_auth(is_success: bool) -> Any: | ||||
|     if is_success: | ||||
|         with patch('simple_salesforce.api.Salesforce.__init__') as mock_sf_init: | ||||
|             mock_sf_init.return_value = None | ||||
|             yield | ||||
|     else: | ||||
|         with patch( | ||||
|             'simple_salesforce.api.Salesforce.__init__', | ||||
|             side_effect=SalesforceAuthenticationFailed(403, 'auth failed') | ||||
|         ) as mock_sf_init: | ||||
|             mock_sf_init.return_value = None | ||||
|             yield | ||||
| 
 | ||||
| 
 | ||||
| @contextmanager | ||||
| def mock_salesforce_commands_types() -> Any: | ||||
|     with patch('zulip_bots.bots.salesforce.utils.commands', mock_commands), \ | ||||
|             patch('zulip_bots.bots.salesforce.utils.object_types', mock_object_types): | ||||
|         yield | ||||
| 
 | ||||
| 
 | ||||
| mock_config = { | ||||
|     'username': 'name@example.com', | ||||
|     'password': 'foo', | ||||
|     'security_token': 'abcdefg' | ||||
| } | ||||
| 
 | ||||
| help_text = '''Salesforce bot | ||||
| This bot can do simple salesforce query requests | ||||
| **All commands must be @-mentioned to the bot.** | ||||
| Commands: | ||||
| **find contact <name> [arguments]**: finds contacts | ||||
| **find top opportunities <amount> [arguments]**: finds opportunities | ||||
| 
 | ||||
| Arguments: | ||||
| **-limit <num>**: the maximum number of entries sent (default: 5) | ||||
| **-show**: show all the properties of each entry (default: false) | ||||
| 
 | ||||
| This bot can also show details about any Salesforce links sent to it. | ||||
| 
 | ||||
| Supported Object types: | ||||
| These are the types of Salesforce object supported by this bot. | ||||
| The bot cannot show the details of any other object types. | ||||
| Table | ||||
| Table | ||||
| ''' | ||||
| 
 | ||||
| 
 | ||||
| def echo(arg: str, sf: Any, command: Dict[str, Any]) -> str: | ||||
|     return arg | ||||
| 
 | ||||
| 
 | ||||
| mock_commands = [ | ||||
|     { | ||||
|         'commands': ['find contact'], | ||||
|         'object': 'contact', | ||||
|         'description': 'finds contacts', | ||||
|         'template': 'find contact <name>', | ||||
|     }, | ||||
|     { | ||||
|         'commands': ['find top opportunities'], | ||||
|         'object': 'opportunity', | ||||
|         'query': 'SELECT {} FROM {} WHERE isClosed=false ORDER BY amount DESC LIMIT {}', | ||||
|         'description': 'finds opportunities', | ||||
|         'template': 'find top opportunities <amount>', | ||||
|         'rank_output': True, | ||||
|         'force_keys': ['Amount'], | ||||
|         'exclude_keys': ['Status'], | ||||
|         'show_all_keys': True | ||||
|     }, | ||||
|     { | ||||
|         'commands': ['echo'], | ||||
|         'callback': echo | ||||
|     } | ||||
| ] | ||||
| 
 | ||||
| 
 | ||||
| mock_object_types = { | ||||
|     'contact': { | ||||
|         'fields': 'Id, Name, Phone', | ||||
|         'table': 'Table' | ||||
|     }, | ||||
|     'opportunity': { | ||||
|         'fields': 'Id, Name, Amount, Status', | ||||
|         'table': 'Table' | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| class TestSalesforceBot(BotTestCase): | ||||
|     bot_name = "salesforce"  # type: str | ||||
| 
 | ||||
|     def _test(self, test_name: str, message: str, response: str, auth_success: bool=True) -> None: | ||||
|         with self.mock_config_info(mock_config), \ | ||||
|                 mock_salesforce_auth(auth_success), \ | ||||
|                 mock_salesforce_query(test_name, 'salesforce'), \ | ||||
|                 mock_salesforce_commands_types(): | ||||
|             self.verify_reply(message, response) | ||||
| 
 | ||||
|     def _test_initialize(self, auth_success: bool=True) -> None: | ||||
|         with self.mock_config_info(mock_config), \ | ||||
|                 mock_salesforce_auth(auth_success), \ | ||||
|                 mock_salesforce_commands_types(): | ||||
|             bot, bot_handler = self._get_handlers() | ||||
| 
 | ||||
|     def test_bot_responds_to_empty_message(self) -> None: | ||||
|         self._test('test_one_result', '', help_text) | ||||
| 
 | ||||
|     def test_one_result(self) -> None: | ||||
|         res = '''**[foo](https://login.salesforce.com/foo_id)** | ||||
| >**Phone**: 020 1234 5678 | ||||
| ''' | ||||
|         self._test('test_one_result', 'find contact foo', res) | ||||
| 
 | ||||
|     def test_multiple_results(self) -> None: | ||||
|         res = '**[foo](https://login.salesforce.com/foo_id)**\n**[bar](https://login.salesforce.com/bar_id)**\n' | ||||
|         self._test('test_multiple_results', 'find contact foo', res) | ||||
| 
 | ||||
|     def test_arg_show(self) -> None: | ||||
|         res = '''**[foo](https://login.salesforce.com/foo_id)** | ||||
| >**Phone**: 020 1234 5678 | ||||
| 
 | ||||
| **[bar](https://login.salesforce.com/bar_id)** | ||||
| >**Phone**: 020 5678 1234 | ||||
| 
 | ||||
| ''' | ||||
|         self._test('test_multiple_results', 'find contact foo -show', res) | ||||
| 
 | ||||
|     def test_no_results(self) -> None: | ||||
|         self._test('test_no_results', 'find contact foo', 'No records found.') | ||||
| 
 | ||||
|     def test_rank_and_force_keys(self) -> None: | ||||
|         res = '''1) **[foo](https://login.salesforce.com/foo_id)** | ||||
| >**Amount**: 2 | ||||
| 
 | ||||
| 2) **[bar](https://login.salesforce.com/bar_id)** | ||||
| >**Amount**: 1 | ||||
| 
 | ||||
| ''' | ||||
|         self._test('test_top_opportunities', 'find top opportunities 2', res) | ||||
| 
 | ||||
|     def test_limit_arg(self) -> None: | ||||
|         res = '''**[foo](https://login.salesforce.com/foo_id)** | ||||
| >**Phone**: 020 1234 5678 | ||||
| ''' | ||||
|         with self.assertLogs(level='INFO') as log: | ||||
|             self._test('test_one_result', 'find contact foo -limit 1', res) | ||||
|             self.assertIn('INFO:root:Searching with limit 1', log.output) | ||||
| 
 | ||||
|     def test_help(self) -> None: | ||||
|         self._test('test_one_result', 'help', help_text) | ||||
|         self._test('test_one_result', 'foo bar baz', help_text) | ||||
|         self._test('test_one_result', 'find contact', | ||||
|                    'Usage: find contact <name> [arguments]') | ||||
| 
 | ||||
|     def test_bad_auth(self) -> None: | ||||
|         with self.assertLogs(level='ERROR') as log, \ | ||||
|                 patch('builtins.quit'): | ||||
|             self._test_initialize(auth_success=False) | ||||
|             self.assertIn( | ||||
|                 'ERROR:root:Failed to log in to Salesforce. 403 auth failed', log.output) | ||||
| 
 | ||||
|     def test_callback(self) -> None: | ||||
|         self._test('test_one_result', 'echo hello', 'hello') | ||||
| 
 | ||||
|     def test_link_normal(self) -> None: | ||||
|         res = '''**[foo](https://login.salesforce.com/foo_id)** | ||||
| >**Phone**: 020 1234 5678 | ||||
| ''' | ||||
|         self._test('test_one_result', | ||||
|                    'https://login.salesforce.com/1c3e5g7i9k1m3o5q7s', res) | ||||
| 
 | ||||
|     def test_link_invalid(self) -> None: | ||||
|         self._test('test_one_result', | ||||
|                    'https://login.salesforce.com/foo/bar/1c3e5g7$i9k1m3o5q7', | ||||
|                    'Invalid salesforce link') | ||||
| 
 | ||||
|     def test_link_no_results(self) -> None: | ||||
|         res = 'No object found. Make sure it is of the supported types. Type `help` for more info.' | ||||
|         self._test('test_no_results', | ||||
|                    'https://login.salesforce.com/1c3e5g7i9k1m3o5q7s', res) | ||||
							
								
								
									
										47
									
								
								zulip_bots/zulip_bots/bots/salesforce/utils.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								zulip_bots/zulip_bots/bots/salesforce/utils.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,47 @@ | |||
| link_query = 'SELECT {} FROM {} WHERE Id=\'{}\'' | ||||
| default_query = 'SELECT {} FROM {} WHERE Name LIKE \'%{}%\' LIMIT {}' | ||||
| 
 | ||||
| commands = [ | ||||
|     { | ||||
|         'commands': ['search account', 'find account', 'search accounts', 'find accounts'], | ||||
|         'object': 'account', | ||||
|         'description': 'Returns a list of accounts of the name specified', | ||||
|         'template': 'search account <name>' | ||||
|     }, | ||||
|     { | ||||
|         'commands': ['search contact', 'find contact', 'search contacts', 'find contacts'], | ||||
|         'object': 'contact', | ||||
|         'description': 'Returns a list of contacts of the name specified', | ||||
|         'template': 'search contact <name>' | ||||
|     }, | ||||
|     { | ||||
|         'commands': ['search opportunity', 'find opportunity', 'search opportunities', 'find opportunities'], | ||||
|         'object': 'opportunity', | ||||
|         'description': 'Returns a list of opportunities of the name specified', | ||||
|         'template': 'search opportunity <name>' | ||||
|     }, | ||||
|     { | ||||
|         'commands': ['search top opportunity', 'find top opportunity', 'search top opportunities', 'find top opportunities'], | ||||
|         'object': 'opportunity', | ||||
|         'query': 'SELECT {} FROM {} WHERE isClosed=false ORDER BY amount DESC LIMIT {}', | ||||
|         'description': 'Returns a list of opportunities organised by amount', | ||||
|         'template': 'search top opportunities <amount>', | ||||
|         'rank_output': True, | ||||
|         'force_keys': ['Amount'] | ||||
|     } | ||||
| ]  # type: List[Dict[str, Any]] | ||||
| 
 | ||||
| object_types = { | ||||
|     'account': { | ||||
|         'fields': 'Id, Name, Phone, BillingStreet, BillingCity, BillingState', | ||||
|         'table': 'Account' | ||||
|     }, | ||||
|     'contact': { | ||||
|         'fields': 'Id, Name, Phone, MobilePhone, Email', | ||||
|         'table': 'Contact' | ||||
|     }, | ||||
|     'opportunity': { | ||||
|         'fields': 'Id, Name, Amount, Probability, StageName, CloseDate', | ||||
|         'table': 'Opportunity' | ||||
|     } | ||||
| }  # type: Dict[str, Dict[str, str]] | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 fredfishgames
						fredfishgames