def ensure_ticket(self, issue): ticket = None ticket_id = self.store.hget('issue_to_ticket_id', issue.key) if not ticket_id: ticket_id = getattr(issue.fields, self.jira_reference_field) if ticket_id: ticket = self.sfdc_client.ticket(ticket_id) if not ticket: LOG.debug('Jira-issue has a link to SF-ticket, but ' 'SF does not have a ticket with ID: %s', ticket_id) if not ticket: if not self.is_issue_eligible(issue): return False LOG.info('Creating SF ticket for JIRA issue %s', issue.key) ticket_id = self.create_ticket(issue) elif ticket['Status__c'] == self.sf_ticket_close_status and not ticket['Closed__c']: self.sfdc_client.update_ticket(ticket['Id'], data={'Closed__c': True}) elif ticket['Status__c'] == self.sf_ticket_close_status: if not self.is_issue_eligible(issue): return False LOG.info('Creating followup SF ticket for ' 'JIRA issue %s', issue.key) ticket_id = self.create_followup_ticket(issue, ticket_id) self.store.hset('issue_to_ticket_id', issue.key, ticket_id) return self.sfdc_client.ticket(ticket_id)
def sync_comments_to_jira(self, issue, ticket): comments = self.sfdc_client.ticket_comments(ticket['Id']) for comment in comments: if self.store.sismember('seen_comments_id', comment['external_id__c']): LOG.debug('Skipping seen SalesForce comment: %s', comment['Id']) continue if comment['external_id__c']: LOG.debug('Skipping seen SalesForce comment: %s ' '(comment has JIRA comment-id %s )', comment['Id'], comment['external_id__c']) continue LOG.info( 'Copying SalesForce comment %s , to JIRA issue %s', comment['Id'], issue.key) comment_body = self.jira_comment_format.render(comment=comment['Comment__c'], created_at=comment['CreatedDate'], created_by=comment['CreatedBy']['Name']) issue_comment = self.jira_client.add_comment(issue, comment_body) data = {'external_id__c': issue_comment.id} LOG.info( 'Update SalesForce comment %s, with JIRA comment-id: %s', comment['Id'], issue_comment.id) self.sfdc_client.update_comment(comment['Id'], data) self.store.sadd('seen_comments_id', issue_comment.id)
def sync_issues(self): LOG.debug('Querying JIRA: %s', self.issue_jql) for issue in self.jira_client.search_issues(self.issue_jql, maxResults=300, fields='assignee,attachment,comment,*navigable'): try: self.sync_issue(issue) except KeyboardInterrupt: LOG.error('Operation canceled by user') return except: LOG.exception('Failed to sync issue: %s', issue.key) LOG.debug('Sync finished')
def sync_issue(self, issue): LOG.debug('Syncing JIRA issue: %s', issue.key) ticket = self.ensure_ticket(issue) if not ticket: return self.sync_priority(issue, ticket) self.sync_jira_reference(issue, ticket) self.sync_assignee(issue, ticket) self.sync_comments_from_jira(issue, ticket) self.sync_comments_to_jira(issue, ticket) self.sync_subject_description(issue, ticket) self.sync_status(issue, ticket)
def sync_assignee(self, issue, ticket): LOG.info('START ASSIGNEE for ticket-id %s and issue %s', ticket['Id'], issue.key) self.force_assignee = False last_seen_jira_assignee = self.store.get('last_seen_jira_assignee:{}'.format(issue.key)) last_seen_sf_assignee = self.store.get('last_seen_sf_assignee:{}'.format(ticket['Id'])) LOG.info('jira-issue %s ; last_seen_jira_assignee %s', issue.key, last_seen_jira_assignee) LOG.info('ticket-id %s ; last_seen_sf_assignee %s', ticket['Id'], last_seen_sf_assignee) if issue.fields.assignee: LOG.debug('JIRA issue assigned: %s', issue.fields.assignee.name) elif not issue.fields.assignee: LOG.info('Assigning previously unassigned JIRA issue to bot') self.jira_client.assign_issue(issue, self.jira_identity) issue = self.refresh_issue(issue) ticket_assignee_name = current_ticket_assignee_name = ticket['Assignee__c'] jira_assignee_name = getattr(issue.fields.assignee, 'name', None) if issue.fields.assignee.name != last_seen_jira_assignee: if jira_assignee_name != self.jira_identity: ticket_assignee_name = self.assignee_sf_name[0] else: ticket_assignee_name = self.assignee_sf_name[1] data = {'Assignee__c': ticket_assignee_name} if (current_ticket_assignee_name == self.assignee_sf_name[0] and ticket_assignee_name == self.assignee_sf_name[1]): self.force_assignee = True self.sfdc_client.update_ticket(ticket['Id'], data) elif ticket['Assignee__c'] != last_seen_sf_assignee: if ticket['Assignee__c'] == self.assignee_sf_name[1]: jira_assignee_name = self.jira_identity elif ticket['Assignee__c'] == self.assignee_sf_name[0]: jira_assignee_name = self.symantec_assignee_username self.jira_client.assign_issue(issue, jira_assignee_name) LOG.info('ticket-id %s ; ticket_assignee_name %s', ticket['Id'], ticket_assignee_name) LOG.info('jira-issue %s ; jira_assignee_name %s', issue.key, jira_assignee_name) LOG.info('FINISHED ASSIGNEE for ticket-id %s and issue %s', ticket['Id'], issue.key) self.store.set('last_seen_jira_assignee:{}'.format(issue.key), jira_assignee_name) self.store.set('last_seen_sf_assignee:{}'.format(ticket['Id']), ticket_assignee_name)
def is_issue_eligible(self, issue): """ Determines if an untracked or previously closed issue is eligible for creation in SF :param issue: `jira.resources.Issue` object :return: Whether or not issue is eligible """ if issue.fields.status.name in self.jira_solved_statuses: # Ignore issues that have already been marked solved LOG.debug('Skipping issue %s (issues that have already ' 'been marked solved)', issue.key) return False if issue.fields.assignee and issue.fields.assignee.name != self.jira_identity: # FIXME # Ignore issues that have already been assigned to someone other than us LOG.debug('Skipping issue %s (issues that have already ' 'been assigned to someone other than us)', issue.key) return False return True
def sync_comments_from_jira(self, issue, ticket): for comment in issue.fields.comment.comments: if comment.author.name == self.jira_identity: LOG.debug('Skipping my own JIRA comment: %s', comment.id) continue if self.store.sismember('seen_comments_id', comment.id): LOG.debug('Skipping seen JIRA comment: %s', comment.id) continue else: comment_from_sf = self.sfdc_client.ticket_comment(comment.id) if comment_from_sf['totalSize'] != 0: LOG.debug('Skipping seen SF comment: %s', comment.id) self.store.sadd('seen_comments_id', comment.id) continue LOG.info('Copying JIRA (Jira issue %s) comment to SFDC: %s', issue.key, comment.id) comment_body = self.sf_comment_format.render(comment=comment) data = { 'Comment__c': comment_body, 'related_id__c': ticket['Id'], 'external_id__c': comment.id } self.sfdc_client.create_ticket_comment(data) self.store.sadd('seen_comments_id', comment.id)
def create_followup_ticket(self, issue, closed_ticket_id): LOG.debug('Trying to create new ticket for re-opened issue %s', issue.key) assignee_name = getattr(issue.fields.assignee, 'name', self.jira_identity) reporter = getattr(issue.fields.reporter, 'displayName', '') comment = self.sf_followup_comment_format.render(issue=issue, jira_url=self.jira_url) data = { 'Subject__c': issue.fields.summary, 'Description__c': getattr(issue.fields, 'description', ''), 'External_id__c': issue.key, 'Requester__c': reporter, 'Assignee__c': assignee_name, 'Closed_Case_Id__c': closed_ticket_id, 'Status__c': self.jira_possible_status['New'] } result = self.sfdc_client.create_ticket(data) LOG.debug('Successful create new ticket %s, for old issue %s', result['id'], issue.key) ticket = self.sfdc_client.ticket(result['id']) # description = self._description_followup_ticket(getattr(issue.fields, 'description', ''), ticket) # data = { # 'Description__c': description, # } # self.sfdc_client.update_ticket(ticket['Id'], data) issue.update(fields={self.jira_sf_case_number_field: ticket['CaseNumber__c']}) data = { 'Comment__c': comment, 'related_id__c': result['id'], } self.sfdc_client.create_ticket_comment(data) LOG.debug('Start bind old jira comments for new ticket') self._change_sf_comments_id(issue, result['id']) LOG.debug('Finish bind old jira comments for new ticket') # followup ticket's status always set to 'New' self.store.set('last_seen_jira_status:{}'.format(issue.key), issue.fields.status.name) self.store.set('last_seen_sf_status:{}'.format(ticket['Id']), self.jira_possible_status['New']) return result['id']
def sync_status(self, issue, ticket): last_seen_jira_status = self.store.get('last_seen_jira_status:{}'.format(issue.key)) last_seen_sf_status = self.store.get('last_seen_sf_status:{}'.format(ticket['Id'])) LOG.debug('JIRA status: %s; SF status: %s', issue.fields.status.name, ticket['Status__c']) jira_status_changed = last_seen_jira_status != issue.fields.status.name if jira_status_changed: LOG.debug('JIRA status changed') sf_status_changed = last_seen_sf_status != ticket['Status__c'] if sf_status_changed: LOG.debug('SF status changed') if jira_status_changed or sf_status_changed or self.force_assignee: new_issue_status, new_ticket_status = self.new_process_sync_status( issue, ticket, jira_status_changed, sf_status_changed) self.store.set('last_seen_jira_status:{}'.format(issue.key), new_issue_status) self.store.set('last_seen_sf_status:{}'.format(ticket['Id']), new_ticket_status)
def new_process_sync_status(self, issue, ticket, jira_status_changed, sf_status_changed): owned = issue.fields.assignee.name == self.jira_identity real_owner = self.refresh_issue(issue).fields.assignee.name status_name_issue = issue.fields.status.name if self.force_assignee and not sf_status_changed and not jira_status_changed: # If issue was only unassigned to bot - need set SF-ticket status to Open new_sf_status = self.jira_possible_status['Support Investigating'] data = { 'Status__c': new_sf_status } self.sfdc_client.update_ticket(ticket['Id'], data) LOG.debug('Updated ticket status: %s', ticket['Id']) return status_name_issue, new_sf_status if sf_status_changed and not jira_status_changed: jira_status_from_conf = self.reference_jira_sf_statuses.get(status_name_issue) possible_ticket_status = jira_status_from_conf.get(ticket['Status__c']) if not jira_status_from_conf or not possible_ticket_status: LOG.info('You try to change SF status. ' 'Jira status name now %s, ' 'Jira status name from conf %s, ' 'SF status %s, ' 'Possible ticket status %s. ' 'Please see conf file. SF status will not change', status_name_issue, jira_status_from_conf, ticket['Status__c'], possible_ticket_status) sf_status_changed = True jira_status_changed = True if sf_status_changed and not jira_status_changed: workflow = self.reference_jira_sf_statuses.get(status_name_issue).get(ticket['Status__c']) # If we (L1/L2) try to move case to solve-status, when it has Symantec-asignee if (real_owner != self.jira_identity) and (ticket['Status__c'] in self.sf_ticket_solve_status or ticket['Status__c'] == 'Pending'): self.jira_client.assign_issue(issue, self.jira_identity) #issue = self.refresh_issue(issue) if not workflow: LOG.info('Not found schema. Current Jira status: %s,' ' SF status: %s', status_name_issue, ticket['Status__c']) transitions = self.jira_client.transitions(self.jira_client.issue(issue.key)) available_transitions = dict((t['name'], t['id']) for t in transitions) LOG.info('For current Jira-state %s, possible statuses is: %s, List of moving statuses %s ', status_name_issue, available_transitions, workflow) if (real_owner != self.jira_identity) and ticket['Status__c'] == 'On Hold': workflow = ['Skip', ] for status in workflow: if status == 'Skip': continue LOG.info('Try to move issue to %s status', status) result = self.jira_client.transition_issue(issue, available_transitions[status]) LOG.info('Moved issue to %s status', status) transitions = self.jira_client.transitions(self.jira_client.issue(issue.key)) available_transitions = dict((t['name'], t['id']) for t in transitions) LOG.info('Now, possible statuses: %s ', available_transitions) issue = self.refresh_issue(issue) self._revert_assignee(issue, real_owner) return issue.fields.status.name, ticket['Status__c'] else: new_sf_status = self.map_status_jira_sf(status_name_issue) if new_sf_status in self.sf_ticket_solve_status: data = { 'Status__c': new_sf_status } elif status_name_issue in ['Waiting Support', 'Waiting Reporter', 'Support Investigating']: if real_owner != self.jira_identity: new_sf_status = 'On Hold' data = { 'Status__c': new_sf_status } else: new_sf_status = 'Open' data = { 'Status__c': new_sf_status } elif ticket['Status__c'] == 'On Hold' and not owned: return status_name_issue, ticket['Status__c'] elif ticket['Status__c'] == 'On Hold' and owned: data = { 'Status__c': new_sf_status } else: data = { 'Status__c': new_sf_status } self.sfdc_client.update_ticket(ticket['Id'], data) LOG.debug('Updated ticket status: %s', ticket['Id']) return status_name_issue, new_sf_status