Example #1
0
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'