def process_calls(): """ Go through all "Back Office Third Party/CSC" calls, classifying if open or closed :return: """ try: sdplus_api = API(os.environ['SDPLUS_ADMIN'], 'http://sdplus/sdpapi/') if not sdplus_api: raise KeyError except KeyError: print( 'Windows environment varible for "SDPLUS_ADMIN" (the API key for sdplus) wasn\'t found. \n' 'Please correct using ""setx SDPLUS_ADMIN <insert your own SDPLUS key here>" in a command line.' ) sys.exit(1) result = [] all_queues = sdplus_api.request_get_requests( 'Back Office Third Party/CSC_QUEUE') for each_call in all_queues: conversations = sdplus_api.request_get_all_conversations( each_call['workorderid']) each_call['classification'] = classify_call(conversations) each_call['Others involved'] = find_all_people_involved( conversations, each_call['requester']) each_call['CSC open/reopen date'] = find_date_csc_opened_call( conversations) each_call['CSC severity'] = find_csc_severity(conversations) result.append(each_call) return result
class RequestGeneralTest(unittest.TestCase): def setUp(self): self.sdplus_api = API(sdplus_api_key, sdplus_base_url) def test_requests_get_requests(self): result = self.sdplus_api.request_get_requests() self.assertEqual(result['response_status'], 'Success') def test_request_get_request_filters(self): result = self.sdplus_api.request_get_request_filters() self.assertEqual(result['response_status'], 'Success')
class NotesTest(unittest.TestCase): def setUp(self): self.sdplus_api = API(sdplus_api_key, sdplus_base_url) fields = { 'reqtemplate': 'Default Request', # or 'General IT Request' which has supplier ref, but also Due by Date, 'requesttype': 'Service Request', 'status': 'Hold - Awaiting Third Party', 'requester': 'Simon Crouch', 'mode': '@Southmead Retained Estate', # site 'best contact number': '-', 'Exact Location': 'white room', 'group': 'Back Office Third Party/CSC', 'assignee': 'Simon Crouch', 'subject': 'This is a test call only - please ignore', 'description': 'This is a test call only (description) - please ignore', 'service': '.Lorenzo/Galaxy - IT Templates', # Service Category 'category': 'Clinical Applications Incident', # Self Service Incident 'subcategory': 'Lorenzo', 'impact': '3 Impacts Department', 'urgency': '3 Business Operations Slightly Affected - Requires response within 8 hours of created time' } result = self.sdplus_api.request_add(fields) self.request_id = result['workorderid'] print('(Created call ' + self.request_id + ')') def tearDown(self): result = self.sdplus_api.request_delete(self.request_id) print(result['response_status']) def test_note_add(self): result = self.sdplus_api.note_add(self.request_id, text='Test note add') self.assertEqual(result['response_status'], 'Success') def test_note_view_all(self): result = self.sdplus_api.note_view_all(self.request_id) self.assertEqual(result['response_status'], 'Success')
class TechnicianTest(unittest.TestCase): def setUp(self): self.sdplus_api = API(sdplus_api_key, sdplus_base_url) def test_technician_get_all(self): result = self.sdplus_api.technician_get_all() self.assertEqual(result['response_status'], 'Success')
class RequestSpecificTest(unittest.TestCase): def setUp(self): self.sdplus_api = API(sdplus_api_key, sdplus_base_url) self.request_id = '198952' self.conversation_id = '577291' self.notification_id = '855331' def test_request_get_conversation(self): result = self.sdplus_api.request_get_conversation( self.request_id, self.conversation_id) self.assertEqual(result['response_status'], 'Success') def test_request_get_notification(self): result = self.sdplus_api.request_get_notification( self.request_id, self.notification_id) self.assertEqual(result['response_status'], 'Success')
class CustomTest(unittest.TestCase): def setUp(self): self.sdplus_api = API(sdplus_api_key, sdplus_base_url) self.request_id = '198952' def test_request_assign_name(self): result = self.sdplus_api.request_assign_name('Simon Crouch', self.request_id) self.assertEqual(result['response_status'], 'Success')
class NotesSpecificTest(unittest.TestCase): def setUp(self): self.sdplus_api = API(sdplus_api_key, sdplus_base_url) self.request_id = '198952' self.note_id = '855331' def test_note_view(self): result = self.sdplus_api.note_view(self.request_id, self.note_id) self.assertEqual(result['response_status'], 'Success') def test_note_view_delete(self): result = self.sdplus_api.note_delete(self.request_id, self.note_id) self.assertEqual(result['response_status'], 'Success') def test_note_edit(self): result = self.sdplus_api.note_edit(self.request_id, self.note_id, text='Test note edit') self.assertEqual(result['response_status'], 'Success')
def setUp(self): self.attachment_path = r'c:\file.txt' self.technician_id = '12345' self.sdplus_api = API(sdplus_api_key, sdplus_base_url) fields = { 'reqtemplate': 'Default Request', # or 'General IT Request' which has supplier ref, but also Due by Date, 'requesttype': 'Service Request', 'status': 'Hold - Awaiting Third Party', 'requester': 'Simon Crouch', 'mode': '@Southmead Retained Estate', # site 'best contact number': '-', 'Exact Location': 'white room', 'group': 'Back Office Third Party/CSC', 'assignee': 'Simon Crouch', 'subject': 'This is a test call only - please ignore', 'description': 'This is a test call only (description) - please ignore', 'service': '.Lorenzo/Galaxy - IT Templates', # Service Category 'category': 'Clinical Applications Incident', # Self Service Incident 'subcategory': 'Lorenzo', 'impact': '3 Impacts Department', 'urgency': '3 Business Operations Slightly Affected - Requires response within 8 hours of created time' } result = self.sdplus_api.request_add(fields) self.request_id = result['workorderid'] print('(Created call ' + self.request_id + ')')
def process_emails(self): """ Process outlook folder for badly-formed sdplus emails, edit subject and resend to help desk :return: None - Forwards emails appending ##131234## in subject and moves to a folder """ mapi = self.outlook.GetNamespace('MAPI') # inbox = mapi.GetDefaultFolder(6) # 6=olFolderInbox=my own inbox recipient = mapi.CreateRecipient('RM1048') # 'Alias' of IT Third Party Response recipient.Resolve() if recipient.Resolved: # https://msdn.microsoft.com/en-us/library/office/ff869575.aspx self.inbox = mapi.GetSharedDefaultFolder(recipient, 6) messages = self.inbox.Items self.sdplus_api = API(self.sdplus_api_key, self.sdplus_api_url) print('Found ' + str(len(messages)) + ' messages to process:') # mailItem.Move changes inbox.Items.Count on a normal loop, but working from count to 0 works well for no in range(messages.Count-1, -1, -1): message = messages[no] hd = self.hd_ref_from_email(message) # sdplus clean, subject if re.search(self.sdplus_clean, message.Subject): sdplus_found_number = re.search(self.sdplus_clean, message.Subject).group(1) print(sdplus_found_number + ': sdplus clean, subject') if self.sdplus_valid(sdplus_found_number): self.update_sdplus(sdplus_found_number, 'Supplier Ref', hd) self.slack_warn_if_not_assigned(sdplus_found_number) self.send_move(message) # sdplus, subject elif re.search(self.sdplus_csc, message.Subject): sdplus_found_number = re.search(self.sdplus_csc, message.Subject).group(1) print(sdplus_found_number + ': sdplus, subject') if self.sdplus_valid(sdplus_found_number): self.update_sdplus(sdplus_found_number, 'Supplier Ref', hd) self.slack_warn_if_not_assigned(sdplus_found_number) self.send_move(message, ' ##' + sdplus_found_number + '##') # sdplus, body elif re.search(self.sdplus_csc, message.Body): sdplus_found_number = re.search(self.sdplus_csc, message.Body).group(1) print(sdplus_found_number + ': sdplus, body') if self.sdplus_valid(sdplus_found_number): self.update_sdplus(sdplus_found_number, 'Supplier Ref', hd) self.slack_warn_if_not_assigned(sdplus_found_number) self.send_move(message, ' ##' + sdplus_found_number + '##') # sdplus, body, remove_no #s elif re.search(self.sdplus_csc, message.Body.replace('#', '')): sdplus_found_number = re.search(self.sdplus_csc, message.Body.replace('#', '')).group(1) print(sdplus_found_number + ': sdplus, body, remove_no #s') if self.sdplus_valid(sdplus_found_number): self.update_sdplus(sdplus_found_number, 'Supplier Ref', hd) self.slack_warn_if_not_assigned(sdplus_found_number) self.send_move(message, ' ##' + sdplus_found_number + '##') else: print("Can't work out sdplus number")
class OutlookSDPlus: """ Process outlook folder for manage engine helpdesk number, confirm number is valid, forward email into Manage Engine appending the local reference number with ##s """ def __init__(self): print('Remedy email processor v' + __version__) self.outlook = win32com.client.Dispatch('Outlook.Application') self.inbox = None self.sdplus_api = None self.sdplus_api_key = os.environ['SDPLUS_ADMIN'] self.sdplus_api_url = 'http://sdplus/sdpapi/request/' self.sdplus_clean = r'(?:##)(\d{6})(?:##)' # clean sdplus number is group 1 as group(0)=entire match self.sdplus_csc = r'(?:NBNT|NBNTSD)(\d{6})' # clean sdplus number is group 1 self.hd_ref = r'(?:HD0*)(\d{7}\b)' # clean 7 digit HD number is group 1 (match HD, 0 or infinite zeros, ref) self.service_desk_to = '*****@*****.**' self.destination_folder_name = 'Processed' self.slack = SlackAPI() def process_emails(self): """ Process outlook folder for badly-formed sdplus emails, edit subject and resend to help desk :return: None - Forwards emails appending ##131234## in subject and moves to a folder """ mapi = self.outlook.GetNamespace('MAPI') # inbox = mapi.GetDefaultFolder(6) # 6=olFolderInbox=my own inbox recipient = mapi.CreateRecipient('RM1048') # 'Alias' of IT Third Party Response recipient.Resolve() if recipient.Resolved: # https://msdn.microsoft.com/en-us/library/office/ff869575.aspx self.inbox = mapi.GetSharedDefaultFolder(recipient, 6) messages = self.inbox.Items self.sdplus_api = API(self.sdplus_api_key, self.sdplus_api_url) print('Found ' + str(len(messages)) + ' messages to process:') # mailItem.Move changes inbox.Items.Count on a normal loop, but working from count to 0 works well for no in range(messages.Count-1, -1, -1): message = messages[no] hd = self.hd_ref_from_email(message) # sdplus clean, subject if re.search(self.sdplus_clean, message.Subject): sdplus_found_number = re.search(self.sdplus_clean, message.Subject).group(1) print(sdplus_found_number + ': sdplus clean, subject') if self.sdplus_valid(sdplus_found_number): self.update_sdplus(sdplus_found_number, 'Supplier Ref', hd) self.slack_warn_if_not_assigned(sdplus_found_number) self.send_move(message) # sdplus, subject elif re.search(self.sdplus_csc, message.Subject): sdplus_found_number = re.search(self.sdplus_csc, message.Subject).group(1) print(sdplus_found_number + ': sdplus, subject') if self.sdplus_valid(sdplus_found_number): self.update_sdplus(sdplus_found_number, 'Supplier Ref', hd) self.slack_warn_if_not_assigned(sdplus_found_number) self.send_move(message, ' ##' + sdplus_found_number + '##') # sdplus, body elif re.search(self.sdplus_csc, message.Body): sdplus_found_number = re.search(self.sdplus_csc, message.Body).group(1) print(sdplus_found_number + ': sdplus, body') if self.sdplus_valid(sdplus_found_number): self.update_sdplus(sdplus_found_number, 'Supplier Ref', hd) self.slack_warn_if_not_assigned(sdplus_found_number) self.send_move(message, ' ##' + sdplus_found_number + '##') # sdplus, body, remove_no #s elif re.search(self.sdplus_csc, message.Body.replace('#', '')): sdplus_found_number = re.search(self.sdplus_csc, message.Body.replace('#', '')).group(1) print(sdplus_found_number + ': sdplus, body, remove_no #s') if self.sdplus_valid(sdplus_found_number): self.update_sdplus(sdplus_found_number, 'Supplier Ref', hd) self.slack_warn_if_not_assigned(sdplus_found_number) self.send_move(message, ' ##' + sdplus_found_number + '##') else: print("Can't work out sdplus number") def sdplus_valid(self, sdplus_ref): result = self.sdplus_api.send(sdplus_ref, 'GET_REQUEST') if result['response_status'] == 'Success': return True else: print('SDPlus number found (' + sdplus_ref + ') is not valid.') return False def hd_ref_from_email(self, message): if re.search(self.hd_ref, message.Subject): return re.search(self.hd_ref, message.Subject).group(1) if re.search(self.hd_ref, message.Body): return re.search(self.hd_ref, message.Body).group(1) def update_sdplus(self, sdplus_ref, field, value): call_current_values = self.sdplus_api.send(sdplus_ref, 'GET_REQUEST') if field in call_current_values or field.lower() in call_current_values: response = self.sdplus_api.send(sdplus_ref, 'EDIT_REQUEST', {field: value}) if response['response_status'] == 'Success': return True else: return False def _is_assigned(self, sdplus): # Check if sdplus call has an Assignee call_details = self.sdplus_api.send(sdplus, 'GET_REQUEST') call_details = dict((k.lower(), v) for k, v in call_details.items()) if call_details['technician']: return True else: return False def slack_warn_if_not_assigned(self, sdplus_ref): if not self._is_assigned(sdplus_ref): sdplus_href = '<http://sdplus/WorkOrder.do?woMode=viewWO&woID={sdplus_ref}|{sdplus_ref}>'\ .format(sdplus_ref=sdplus_ref) # Send message to backoffice group, with @mitch, @simon, @paul self.slack.send('G1FBB4L68', 'Hey <@U1FBYK4BZ>, <@U1F4X362D>, <@U1FA6DMFV> - CSC have responded ' 'to SDPlus {0}, but this is currently unassigned...'.format(sdplus_href)) def send_move(self, mail_item, append_to_subject=''): new_mail = mail_item.Forward() new_mail.To = self.service_desk_to if append_to_subject: new_mail.Subject += append_to_subject self.remove_signature(new_mail) self.insert_line_at_top(new_mail, 'To respond to CSC, please use: ', '*****@*****.**') print('Sending.') new_mail.Send() print('Moving.') mail_item.Move(self.inbox.Folders(self.destination_folder_name)) print('Processed mail successfully.') @staticmethod def remove_signature(mail_object): """ Most not possible without this link: https://msdn.microsoft.com/en-us/library/dd492012(v=office.12).aspx :param mail_object: mailItem object :return: (removes signature) """ # Remove my default signature # https://msdn.microsoft.com/en-us/library/dd492012(v=office.12).aspx print('Attempting to remove_no signature.') if mail_object.BodyFormat == 1: # Plain Text: find_text = '-----Original Message-----' mail_object.Body = mail_object.Body[mail_object.Body.find(find_text) + len(find_text):] if mail_object.BodyFormat == 2 or mail_object.BodyFormat == 3: # HTML or RTF try: active_inspector = mail_object.GetInspector word_doc = active_inspector.WordEditor word_bookmark = word_doc.Bookmarks('_MailAutoSig') if word_bookmark: word_bookmark.Select() word_doc.Windows(1).Selection.Delete() # (HTMLBody property only updated after Display() is called) # assumes you will later call active_inspector.Close(0) (necessary if not calling .Display()) print('Signature removal success.') except pywintypes.com_error: print('Failed to remove_no signature.') return @staticmethod def insert_line_at_top(mail_obj, line, email): """ Most not possible without this link: https://msdn.microsoft.com/en-us/library/dd492012(v=office.12).aspx :param mail_obj: mailItem object :param line: Line to put in top of email :param email: Email to immediate right of line :return: (updates mailItem object) """ if mail_obj.BodyFormat == 1: # Plain text mail_obj.Body = line + email + '\n' + mail_obj.Body elif mail_obj.BodyFormat == 2 or mail_obj.BodyFormat == 3: # HTML or RTF active_inspector = mail_obj.GetInspector word_doc = active_inspector.WordEditor word_selection = word_doc.Windows(1).Selection word_selection.Move(6, -1) # 6=wdStory. Move to beginning word_doc.Characters(1).InsertBefore(line) word_selection.Move(5, 1) # 5=wdLine. Move down 1 line word_selection.Move(1, -1) # 1=wdCharacter. Move back 1 word_doc.Hyperlinks.Add(word_selection.Range, 'mailto:' + email, '', '', email, '') active_inspector.Close(0) # olSave = 0, olDiscard = 1, olPromptForSave = 2
def __init__(self, key, base_url): self.__version__ = '0.2' API.__init__(self, key, base_url)
def setUp(self): self.sdplus_api = API(sdplus_api_key, sdplus_base_url)
def setUp(self): self.sdplus_api = API(sdplus_api_key, sdplus_base_url) self.request_id = '198952'
class RequestTest(unittest.TestCase): def setUp(self): self.attachment_path = r'c:\file.txt' self.technician_id = '12345' self.sdplus_api = API(sdplus_api_key, sdplus_base_url) fields = { 'reqtemplate': 'Default Request', # or 'General IT Request' which has supplier ref, but also Due by Date, 'requesttype': 'Service Request', 'status': 'Hold - Awaiting Third Party', 'requester': 'Simon Crouch', 'mode': '@Southmead Retained Estate', # site 'best contact number': '-', 'Exact Location': 'white room', 'group': 'Back Office Third Party/CSC', 'assignee': 'Simon Crouch', 'subject': 'This is a test call only - please ignore', 'description': 'This is a test call only (description) - please ignore', 'service': '.Lorenzo/Galaxy - IT Templates', # Service Category 'category': 'Clinical Applications Incident', # Self Service Incident 'subcategory': 'Lorenzo', 'impact': '3 Impacts Department', 'urgency': '3 Business Operations Slightly Affected - Requires response within 8 hours of created time' } result = self.sdplus_api.request_add(fields) self.request_id = result['workorderid'] print('(Created call ' + self.request_id + ')') def tearDown(self): result = self.sdplus_api.request_delete(self.request_id) print(result['response_status']) def test_request_close(self): self.sdplus_api.request_pickup(self.request_id) result = self.sdplus_api.request_close(self.request_id, True) self.assertEqual(result['response_status'], 'Success') def test_request_edit(self): result = self.sdplus_api.request_edit( self.request_id, {'subject': 'Test subject - now edited successfully'}) self.assertEqual(result['response_status'], 'Success') def test_request_view(self): result = self.sdplus_api.request_view(self.request_id) self.assertEqual(result['response_status'], 'Success') def test_request_get_conversations(self): result = self.sdplus_api.request_get_conversations(self.request_id) self.assertEqual(result['response_status'], 'Success') def test_request_add_attachment(self): result = self.sdplus_api.request_add_attachment( self.request_id, self.attachment_path) self.assertEqual(result['response_status'], 'Success') def test_request_add_resolution(self): result = self.sdplus_api.request_adding_resolution( self.request_id, 'Resolution test text add') self.assertEqual(result['response_status'], 'Success') def test_request_pickup(self): print(self.test_request_pickup.__name__) result = self.sdplus_api.request_pickup(self.request_id) self.assertEqual(result['response_status'], 'Success') def test_request_assign(self): result = self.sdplus_api.request_assign(self.request_id, self.technician_id) self.assertEqual(result['response_status'], 'Success') def test_request_reply(self): fields = { 'to': '*****@*****.**', 'cc': '*****@*****.**', 'subject': 'subject test reply', 'description': 'Test description reply' } result = self.sdplus_api.request_reply(self.request_id, fields) self.assertEqual(result['response_status'], 'Success') def test_request_get_notifications(self): result = self.sdplus_api.request_get_notifications(self.request_id) self.assertEqual(result['response_status'], 'Success') def test_request_get_all_conversations(self): result = self.sdplus_api.request_get_all_conversations(self.request_id) self.assertEqual(result['response_status'], 'Success') def test_request_editing_resolution(self): self.sdplus_api.request_adding_resolution(self.request_id, 'Resolution text on create') result = self.sdplus_api.request_editing_resolution( self.request_id, 'Resolution test text edit') self.assertEqual(result['response_status'], 'Success') def test_request_get_resolution(self): self.sdplus_api.request_adding_resolution(self.request_id, 'Resolution text on create') result = self.sdplus_api.request_get_resolution(self.request_id) self.assertEqual(result['response_status'], 'Success')
def setUp(self): self.sdplus_api = API(sdplus_api_key, sdplus_base_url) self.request_id = '198952' self.conversation_id = '577291' self.notification_id = '855331'