class JiraTasks(): def __init__(self): self.jira = JIRA(options=secrets.options, basic_auth=secrets.housekeeping_auth) def toggle_watchers(self,action,issue,watch_list=[]): """ Internal method that either adds or removes the watchers of an issue. If it removes them,it returns a list of users that were removed. If it adds, it returns an updated list of watchers. Inputs: :action: String "add"|"remove". The action to take :issue: Issue whose watchers list is being modified :watch_list: list of users. Optional for remove. Required for add. Returns: :issue_watcher: List of users who are or were watching the issue. """ if action=="remove": issue_watchers = self.jira.watchers(issue).watchers for issue_watcher in issue_watchers: self.jira.remove_watcher(issue,issue_watcher.name) else: for old_watcher in watch_list: self.jira.add_watcher(issue,old_watcher) issue_watchers = self.jira.watchers(issue).watchers return issue_watchers
class JiraAlerter(Alerter): """ Creates a Jira ticket for each alert """ required_options = frozenset(['jira_server', 'jira_account_file', 'jira_project', 'jira_issuetype']) # Maintain a static set of built-in fields that we explicitly know how to set # For anything else, we will do best-effort and try to set a string value known_field_list = [ 'jira_account_file', 'jira_assignee', 'jira_bump_in_statuses', 'jira_bump_not_in_statuses', 'jira_bump_tickets', 'jira_component', 'jira_components', 'jira_description', 'jira_ignore_in_title', 'jira_issuetype', 'jira_label', 'jira_labels', 'jira_max_age', 'jira_priority', 'jira_project', 'jira_server', 'jira_watchers', ] # Some built-in jira types that can be used as custom fields require special handling # Here is a sample of one of them: # {"id":"customfield_12807","name":"My Custom Field","custom":true,"orderable":true,"navigable":true,"searchable":true, # "clauseNames":["cf[12807]","My Custom Field"],"schema":{"type":"array","items":"string", # "custom":"com.atlassian.jira.plugin.system.customfieldtypes:multiselect","customId":12807}} # There are likely others that will need to be updated on a case-by-case basis custom_string_types_with_special_handling = [ 'com.atlassian.jira.plugin.system.customfieldtypes:multicheckboxes', 'com.atlassian.jira.plugin.system.customfieldtypes:multiselect', 'com.atlassian.jira.plugin.system.customfieldtypes:radiobuttons', ] def __init__(self, rule): super(JiraAlerter, self).__init__(rule) self.server = self.rule['jira_server'] self.get_account(self.rule['jira_account_file']) self.project = self.rule['jira_project'] self.issue_type = self.rule['jira_issuetype'] # We used to support only a single component. This allows us to maintain backwards compatibility # while also giving the user-facing API a more representative name self.components = self.rule.get('jira_components', self.rule.get('jira_component')) # We used to support only a single label. This allows us to maintain backwards compatibility # while also giving the user-facing API a more representative name self.labels = self.rule.get('jira_labels', self.rule.get('jira_label')) self.description = self.rule.get('jira_description', '') self.assignee = self.rule.get('jira_assignee') self.max_age = self.rule.get('jira_max_age', 30) self.priority = self.rule.get('jira_priority') self.bump_tickets = self.rule.get('jira_bump_tickets', False) self.bump_not_in_statuses = self.rule.get('jira_bump_not_in_statuses') self.bump_in_statuses = self.rule.get('jira_bump_in_statuses') self.watchers = self.rule.get('jira_watchers') if self.bump_in_statuses and self.bump_not_in_statuses: msg = 'Both jira_bump_in_statuses (%s) and jira_bump_not_in_statuses (%s) are set.' % \ (','.join(self.bump_in_statuses), ','.join(self.bump_not_in_statuses)) intersection = list(set(self.bump_in_statuses) & set(self.bump_in_statuses)) if intersection: msg = '%s Both have common statuses of (%s). As such, no tickets will ever be found.' % ( msg, ','.join(intersection)) msg += ' This should be simplified to use only one or the other.' logging.warning(msg) self.jira_args = {'project': {'key': self.project}, 'issuetype': {'name': self.issue_type}} if self.components: # Support single component or list if type(self.components) != list: self.jira_args['components'] = [{'name': self.components}] else: self.jira_args['components'] = [{'name': component} for component in self.components] if self.labels: # Support single label or list if type(self.labels) != list: self.labels = [self.labels] self.jira_args['labels'] = self.labels if self.watchers: # Support single watcher or list if type(self.watchers) != list: self.watchers = [self.watchers] if self.assignee: self.jira_args['assignee'] = {'name': self.assignee} try: self.client = JIRA(self.server, basic_auth=(self.user, self.password)) self.get_priorities() self.get_arbitrary_fields() except JIRAError as e: # JIRAError may contain HTML, pass along only first 1024 chars raise EAException("Error connecting to JIRA: %s" % (str(e)[:1024])) try: if self.priority is not None: self.jira_args['priority'] = {'id': self.priority_ids[self.priority]} except KeyError: logging.error("Priority %s not found. Valid priorities are %s" % (self.priority, self.priority_ids.keys())) def get_arbitrary_fields(self): # This API returns metadata about all the fields defined on the jira server (built-ins and custom ones) fields = self.client.fields() for jira_field, value in self.rule.iteritems(): # If we find a field that is not covered by the set that we are aware of, it means it is either: # 1. A built-in supported field in JIRA that we don't have on our radar # 2. A custom field that a JIRA admin has configured if jira_field.startswith('jira_') and jira_field not in self.known_field_list: # Remove the jira_ part. Convert underscores to spaces normalized_jira_field = jira_field[5:].replace('_', ' ').lower() # All jira fields should be found in the 'id' or the 'name' field. Therefore, try both just in case for identifier in ['name', 'id']: field = next((f for f in fields if normalized_jira_field == f[identifier].replace('_', ' ').lower()), None) if field: break if not field: # Log a warning to ElastAlert saying that we couldn't find that type? # OR raise and fail to load the alert entirely? Probably the latter... raise Exception("Could not find a definition for the jira field '{0}'".format(normalized_jira_field)) arg_name = field['id'] # Check the schema information to decide how to set the value correctly # If the schema information is not available, raise an exception since we don't know how to set it # Note this is only the case for two built-in types, id: issuekey and id: thumbnail if not ('schema' in field or 'type' in field['schema']): raise Exception("Could not determine schema information for the jira field '{0}'".format(normalized_jira_field)) arg_type = field['schema']['type'] # Handle arrays of simple types like strings or numbers if arg_type == 'array': # As a convenience, support the scenario wherein the user only provides # a single value for a multi-value field e.g. jira_labels: Only_One_Label if type(value) != list: value = [value] array_items = field['schema']['items'] # Simple string types if array_items in ['string', 'date', 'datetime']: # Special case for multi-select custom types (the JIRA metadata says that these are strings, but # in reality, they are required to be provided as an object. if 'custom' in field['schema'] and field['schema']['custom'] in self.custom_string_types_with_special_handling: self.jira_args[arg_name] = [{'value': v} for v in value] else: self.jira_args[arg_name] = value elif array_items == 'number': self.jira_args[arg_name] = [int(v) for v in value] # Also attempt to handle arrays of complex types that have to be passed as objects with an identifier 'key' else: # Try setting it as an object, using 'name' as the key # This may not work, as the key might actually be 'key', 'id', 'value', or something else # If it works, great! If not, it will manifest itself as an API error that will bubble up self.jira_args[arg_name] = [{'name': v} for v in value] # Handle non-array types else: # Simple string types if arg_type in ['string', 'date', 'datetime']: # Special case for custom types (the JIRA metadata says that these are strings, but # in reality, they are required to be provided as an object. if 'custom' in field['schema'] and field['schema']['custom'] in self.custom_string_types_with_special_handling: self.jira_args[arg_name] = {'value': value} else: self.jira_args[arg_name] = value # Number type elif arg_type == 'number': self.jira_args[arg_name] = int(value) # Complex type else: self.jira_args[arg_name] = {'name': value} def get_priorities(self): """ Creates a mapping of priority index to id. """ priorities = self.client.priorities() self.priority_ids = {} for x in range(len(priorities)): self.priority_ids[x] = priorities[x].id def set_assignee(self, assignee): self.assignee = assignee if assignee: self.jira_args['assignee'] = {'name': assignee} elif 'assignee' in self.jira_args: self.jira_args.pop('assignee') def find_existing_ticket(self, matches): # Default title, get stripped search version if 'alert_subject' not in self.rule: title = self.create_default_title(matches, True) else: title = self.create_title(matches) if 'jira_ignore_in_title' in self.rule: title = title.replace(matches[0].get(self.rule['jira_ignore_in_title'], ''), '') # This is necessary for search to work. Other special characters and dashes # directly adjacent to words appear to be ok title = title.replace(' - ', ' ') title = title.replace('\\', '\\\\') date = (datetime.datetime.now() - datetime.timedelta(days=self.max_age)).strftime('%Y-%m-%d') jql = 'project=%s AND summary~"%s" and created >= "%s"' % (self.project, title, date) if self.bump_in_statuses: jql = '%s and status in (%s)' % (jql, ','.join(self.bump_in_statuses)) if self.bump_not_in_statuses: jql = '%s and status not in (%s)' % (jql, ','.join(self.bump_not_in_statuses)) try: issues = self.client.search_issues(jql) except JIRAError as e: logging.exception("Error while searching for JIRA ticket using jql '%s': %s" % (jql, e)) return None if len(issues): return issues[0] def comment_on_ticket(self, ticket, match): text = unicode(JiraFormattedMatchString(self.rule, match)) timestamp = pretty_ts(lookup_es_key(match, self.rule['timestamp_field'])) comment = "This alert was triggered again at %s\n%s" % (timestamp, text) self.client.add_comment(ticket, comment) def alert(self, matches): title = self.create_title(matches) if self.bump_tickets: ticket = self.find_existing_ticket(matches) if ticket: elastalert_logger.info('Commenting on existing ticket %s' % (ticket.key)) for match in matches: try: self.comment_on_ticket(ticket, match) except JIRAError as e: logging.exception("Error while commenting on ticket %s: %s" % (ticket, e)) if self.pipeline is not None: self.pipeline['jira_ticket'] = ticket self.pipeline['jira_server'] = self.server return None self.jira_args['summary'] = title self.jira_args['description'] = self.create_alert_body(matches) try: self.issue = self.client.create_issue(**self.jira_args) # You can not add watchers on initial creation. Only as a follow-up action if self.watchers: for watcher in self.watchers: try: self.client.add_watcher(self.issue.key, watcher) except Exception as ex: # Re-raise the exception, preserve the stack-trace, and give some # context as to which watcher failed to be added raise Exception("Exception encountered when trying to add '{0}' as a watcher. Does the user exist?\n{1}" .format(watcher, ex)), None, sys.exc_info()[2] except JIRAError as e: raise EAException("Error creating JIRA ticket: %s" % (e)) elastalert_logger.info("Opened Jira ticket: %s" % (self.issue)) if self.pipeline is not None: self.pipeline['jira_ticket'] = self.issue self.pipeline['jira_server'] = self.server def create_alert_body(self, matches): body = self.description + '\n' body += self.get_aggregation_summary_text(matches) for match in matches: body += unicode(JiraFormattedMatchString(self.rule, match)) if len(matches) > 1: body += '\n----------------------------------------\n' return body def get_aggregation_summary_text(self, matches): text = super(JiraAlerter, self).get_aggregation_summary_text(matches) if text: text = u'{{noformat}}{0}{{noformat}}'.format(text) return text def create_default_title(self, matches, for_search=False): # If there is a query_key, use that in the title if 'query_key' in self.rule and self.rule['query_key'] in matches[0]: title = 'ElastAlert: %s matched %s' % (matches[0][self.rule['query_key']], self.rule['name']) else: title = 'ElastAlert: %s' % (self.rule['name']) if for_search: return title title += ' - %s' % (pretty_ts(matches[0][self.rule['timestamp_field']], self.rule.get('use_local_time'))) # Add count for spikes count = matches[0].get('spike_count') if count: title += ' - %s+ events' % (count) return title def get_info(self): return {'type': 'jira'}
if user: # Make the ticket requester the reporter of the JIRA ticket. logger.debug('Making (' + user.name + ') the reporter of (' + jira_issue.key + ')') syslog.syslog( syslog.LOG_DEBUG, 'Making (' + user.name + ') the reporter of (' + jira_issue.key + ')') jira_issue.update(fields={'reporter': {'name': user.name}}) # Auto-add ticket requester as watcher to the JIRA ticket. logger.debug('Adding (' + user.name + ') as a watcher to (' + jira_issue.key + ')') syslog.syslog( syslog.LOG_DEBUG, 'Adding (' + user.name + ') as a watcher to (' + jira_issue.key + ')') jira.add_watcher(jira_issue, user.name) else: logger.warn( 'Unable to find equivalent RT requester in JIRA: ' + ticket_requester) syslog.syslog( syslog.LOG_WARNING, 'Unable to find equivalent RT requester in JIRA: ' + ticket_requester) # If global watchers are specified, then add them to the newly created ticket. create_watchers = config.get('jira', 'create_watchers') if create_watchers != "None": for create_watcher in create_watchers.split(','): logger.debug('Adding (' + create_watcher + ') as a watcher to (' + jira_issue.key +
class JiraRobot: ROBOT_LIBRARY_SCOPE = 'GLOBAL' jira = None def connect_to_jira(self, JIRAUsername=None, JIRAPassword=None, options=None): """ Connect to a JIRA server. Arguments: | JIRAUsername (string) | (Optional) A JIRA Username you wish to authenticate with, will authorise with anonymous account if left empty | | JIRAPassword (string) | (Optional) If a username was specified and a password is not the user will be prompted for password at runtime | | options (dictionary) | (Optional) A dictionary of options that the JIRA connection will be initialied with | This must be called first to be able to do most JIRA actions such as creating a new issue and assigning users. When connecting to JIRA you may need to authenticate a user. This can be done by passing in Username and Password as parameters. However, should you wish to not have a sensitive password saved in a file, an option is available to not pass in a Password and a prompt will appear asking for the JIRA password at runtime. 'connect to jira' can be called on its own and will default options to: '{'rest_api_version': '2', 'verify': True, 'server': 'http://*****:*****@ssword | {'server': http://devjira01'} | """ if JIRAUsername is not None and (JIRAPassword is "" or JIRAPassword is None): JIRAPassword = getpass.getpass("\nJIRA Password: "******"\nAuthentication to JIRA unsuccessful. Ensure the user used has sufficient access and that Username and Password were correct\n\n" ) sys.exit(1) else: try: self.jira = JIRA(options=JIRAOptions, basic_auth=(str(JIRAUsername), str(JIRAPassword))) except: sys.__stdout__.write( "\nAuthentication to JIRA unsuccessful. Ensure the user used has sufficient access and that Username and Password were correct\n\n" ) sys.exit(1) def create_issue(self, issue_field_dict, assign_current_user=False): """ Creates a new JIRA issue. Arguments: | issue_field_dict (string) | A dictionary in the form of a string that the user can specify the issues fields and field values | | assign_current_user (string/bool) | (Optional) A flag to assign the current user to the issue once it is successfully created, defaults to False | Will create a new issue and returns an Issue Key. The user will use the issue_field_dict variable to specify the issues field and their respective values. This must be in the form of a string written as a dictionary e.g. {'FieldName':'FieldValue'} The field value can also be another dictionary (in some field cases this is needed) e.g. {'FieldName1':{'key':'value'}, 'FieldName2':FieldValue, 'FieldName3':{'name':'value'}} This Create Issue Example page (https://developer.atlassian.com/display/JIRADEV/JIRA+REST+API+Example+-+Create+Issue) is full of useful information in what is required for creating issues, including customfields and issue field value types, it is very important to get the value type right or an error will be thrown. Examples: | *Keyword* | *Parameters* | | | | ${issue_field_dict} | {'project':{'key': 'PROJ'}, 'summary':'Create New Issue', 'description':'Creating a new issue', 'issuetype':{'name': 'Bug'}} | | | connect to jira | asimmons | options= {'http://devjira01'} | | | ${issue} | create issue | ${issue_field_dict} | | | connect to jira | asimmons | options= {'http://devjira01'} | | | ${issue} | create issue | ${issue_field_dict} | True | """ issue_field_dict = eval(str(issue_field_dict)) print(issue_field_dict) new_issue = self.jira.create_issue(issue_field_dict) if assign_current_user is True: self.assign_user_to_issue(new_issue, self.jira.current_user()) return new_issue def create_issue_link(self, link_type, inwardissue, outwardissue, comment=None): """ Create a link between two issues. Arguments: | link_type (string) | The type of link | | inwardissue (string) | The issue to link from | | outwardissue (string) | The issue to link to | | comment (string) | (Optional) A comment to add when joining issues | Example: | *Keyword* | *Parameters* | | | | connect to jira | asimmons | options= {'http://devjira01'} | | | ${issue} | create issue | ${issue_field_dict} | True | | create issue link | relates to | ${issue} | PROJ-385 | """ self.jira.create_issue_link(type=link_type, inwardIssue=str(inwardissue), outwardIssue=str(outwardissue)) def assign_user_to_issue(self, issue, JIRAUsername): # TODO: Review docs """ Adds a user to a specified issue's watcher list Arguments: | issue (string) | A JIRA Issue that a user needs to be assigned to, can be an issue ID or Key | | JIRAUsername (string) | A JIRA Username to assign a user to an issue | Example: | *Keyword* | *Parameters* | | | connect to jira | asimmons | options= {'http://devjira01'} | | ${issue} | create issue | ${issue_field_dict} | | assign user to issue | ${issue} | aSample | """ self.jira.assign_issue(issue=issue, assignee=JIRAUsername) def get_current_user(self): """ Returns the current user used in the Connect to JIRA keyword. """ return self.jira.current_user() def add_watcher_to_issue(self, issue, JIRAUsername): """ Adds a user to a specified issue's watcher list. Arguments: | issue (string) | A JIRA Issue that a watcher needs added to, can be an issue ID or Key | | JIRAUsername (string) | A JIRA Username to add as a watcher to an issue | Example: | *Keyword* | *Parameters* | | | | connect to jira | asimmons | options= {'http://devjira01'} | | | ${issue} | create issue | ${issue_field_dict} | True | | add watcher to issue | ${issue} | aSample | | """ self.jira.add_watcher(issue=issue, watcher=JIRAUsername) def add_comment_to_issue(self, issue, comment, visibility=None): """ Adds a comment to a specified issue from the current user. Arguments: | issue (string) | A JIRA Issue that a watcher needs added to, can be an issue ID or Key | | comment (string) | A body of text to add as a comment to an issue | | visibility (string) | (Optional) | Example: | *Keyword* | *Parameters* | | | | connect to jira | asimmons | options= {'http://devjira01'} | | | ${issue} | create issue | ${issue_field_dict} | True | | add comment to issue | ${issue} | Starting work on this issue | | """ self.jira.add_comment(issue=issue, body=comment) def add_attachment_to_issue(self, issue, attachment, filename=None): """ Uploads and attaches a file a specified issue. (Note: include the file extention when using the 'filename' option or this will change the file type.) Arguments: | issue (string) | A JIRA Issue that a watcher needs added to, can be an issue ID or Key | | attachment (string) | A string pointing to a file location to upload and attach to the issue | | filename (string) | (Optional) A string to rename the file to upon attaching to the issue | Example: | *Keyword* | *Parameters* | | | | connect to jira | asimmons | options= {'http://devjira01'} | | | ${issue} | create issue | ${issue_field_dict} | True | | add attchment to issue | ${issue} | ./logfile.text | LogInformation.txt | """ self.jira.add_attachment(issue=issue, attachment=attachment, filename=filename)
class Housekeeping(): """ This class is the container for all automated Jira functions performed by the Housekeeping agent. """ def __init__(self): # class variables self.ac_label = u'auto-close-24-hours' # open JIRA API Connection self.jira = JIRA(options=secrets.options, basic_auth=secrets.housekeeping_auth) # commands to run self.content_acquisition_auto_qc() self.requeue_free_indexing() self.auto_assign() self.remind_reporter_to_close() self.close_resolved() self.clear_auto_close_label() self.resolved_issue_audit() self.handle_audited_tickets() def content_acquisition_auto_qc(self): """ Takes INDEXREP issues that have been Merged for 30+ minutes and transitions them to Quality Control. It then adds a comment that tags the reporter to inform them that the issue is ready for review. """ # get CA tickets merged 30+ minute ago issues = self.get_issues("auto_qc") for issue in issues: reporter = issue.fields.reporter.key message = '[~%s], this issue is ready for QC.' % reporter """ 771 is the transition ID spedific to this step for this project. Anything more generic will need to parse the transitions list. """ tran_id = self.get_transition_id(issue,"qc") self.jira.transition_issue(issue.key,tran_id) self.jira.add_comment(issue.key, message) def handle_audited_tickets(self): """ Handles audit tickets that are failed. Closed tickets are ignored. Failed tickets trigger the creation of a new ticket in the same project as the original ticket. Inputs: None Returns: None """ issues = self.jira.search_issues( # get all the ADT issues 'project=ADT and status="Failed Audit"') # For each failed issue, generate a new work ticket then close this one for issue in issues: #BUID adt_buid=issue.fields.customfield_10502 #WCID adt_wcid=issue.fields.customfield_10501 #oldBUID adt_old_buid=issue.fields.customfield_13100 #oldWCID adt_old_wcid=issue.fields.customfield_13101 #Indexing Type adt_indexing_type=issue.fields.customfield_10500 #comments adt_comments = [] for comment in self.jira.comments(issue): node = { 'body':self.jira.comment(issue,comment).body, 'author': self.jira.comment(issue,comment).author.key } adt_comments.append(node) link_list = [issue.key,] # first linked ticket should be this audit ticket for link in issue.fields.issuelinks: # grab the rest of links try: link_list.append(link.outwardIssue.key) except AttributeError: pass # capture original ticket and project. If it can't be found, default to INDEXREP as the project try: original_ticket = issue.fields.summary.split("[")[1].split("]")[0] original_project = original_ticket.split("-")[0] except IndexError: original_ticket = "" original_project="INDEXREP" # build the new summary by parsing the audit summary indexrep_summary = issue.fields.summary #build the summary indexrep_summary = indexrep_summary.replace("compliance audit - ","") indexrep_summary = indexrep_summary.split("[")[0] indexrep_summary = ' %s - Failed Audit' % (indexrep_summary) # Build the issue description message = 'This issue failed audit. Please review %s and make any \ necessary corrections.' % original_ticket # Construct the watcher list and de-dupe it watcher_list = [issue.fields.assignee.key,] for w in self.jira.watchers(issue).watchers: watcher_list.append(w.key) watcher_list = set(watcher_list) # get the reporter (reporter is preserved from audit to issue) reporter = issue.fields.reporter.key # Generate the new issue, then close the audit ticket. new_issue = self.make_new_issue(original_project,"",reporter, indexrep_summary,message, watcher_list,link_list,adt_buid, adt_wcid,adt_old_buid,adt_old_wcid, adt_indexing_type,adt_comments) close_me = self.close_issue(issue.key) def resolved_issue_audit(self): """ TAKES issues that have been resolved from specified projectsfor a set interval and creates a new ticket in AUDIT, closes the INDEXREP ticket, and then assigns it to the audit user specified in the self.qa_auditor role. Inputs: None Returns: Error message or Nothing """ # get all the issues from projects in the audit list issues = self.get_issues("audit_list") #build a list of all users in the MS & MD groups member_svc = self.get_group_members("member-services") member_dev = self.get_group_members("membership-development") member_aud = self.get_group_members("issue audits") member_all = [] for user in member_svc: member_all.append(user) #only need the user names, not the meta data for user in member_dev: member_all.append(user) for user in member_aud: member_all.append(user) member_all = set(member_all) #de-dupe # cycle through them and create a new ADT ticket for each for issue in issues: # capture issue fields ind_buid=issue.fields.customfield_10502 #BUID ind_wcid=issue.fields.customfield_10501 #WCID ind_old_buid=issue.fields.customfield_13100 #oldBUID ind_old_wcid=issue.fields.customfield_13101 #oldWCID ind_indexing_type=issue.fields.customfield_10500 #Indexing Type reporter = issue.fields.reporter.key #Reporter link_list = [issue.key,] for link in issue.fields.issuelinks: # grab the rest of links try: link_list.append(link.outwardIssue.key) except AttributeError: pass # build the new ticket summary based on the issue being audited # [ISSUE=123] is used to preserve the original issue key. Replace any brackets with () # to prevent read errors later. adt_summary = issue.fields.summary.replace("[","(").replace("]",")") adt_summary = 'compliance audit - %s [%s]' % (adt_summary,issue.key) # check reporter to see if special consideration is needed # if reporter is not MS or MD, or it's a new member, assign to audit lead. new_member_setup = self.check_for_text(issue, settings.member_setup_strs) assigned_audit_tasks_query = self.get_issues("assigned_audits",True) if reporter not in member_all or new_member_setup: qa_auditor = self.user_with_fewest_issues('issue audits lead', assigned_audit_tasks_query, [reporter]) else: # get the users who can be assigned audit tickets, then select the # one with fewest assigned tickets qa_auditor = self.user_with_fewest_issues('issue audits', assigned_audit_tasks_query, [reporter]) # build the description message = '[~%s], issue %s is ready to audit.' % (qa_auditor, issue.key) #build the watcher list, including original reporter and assignee of the audited ticket watcher_list = [] for w in self.jira.watchers(issue).watchers: watcher_list.append(w.key) try: original_assignee = issue.fields.assignee.key except AttributeError: original_assignee="" # make the audit ticket new_issue = self.make_new_issue("ADT",qa_auditor,reporter, adt_summary,message,watcher_list,link_list,ind_buid, ind_wcid,ind_old_buid,ind_old_wcid,ind_indexing_type) # close the INDEXREP ticket close_me = self.close_issue(issue.key) # add comment to indexrep ticket link_back_comment = "This issue has been closed. The audit ticket is %s" % new_issue self.jira.add_comment(issue.key, link_back_comment) def requeue_free_indexing(self): """ Takes a list of old FCA tickets, and clears the assignee fields in order to allow it to be reassigned. Inputs: None Returns: Nothing """ # get issues that are stale and need reassigned issues = self.get_issues("stale_free") # itirate issues and set assignee to empty. This will allow # auto assignment to set the assignee. for issue in issues: #check for wait in label wait_label = self.label_contains(issue,"wait") # if no wait label, clear the assignee so it can be re-autoassigned if (not wait_label): issue.update(assignee={'name':""}) def make_new_issue(self,project,issue_assignee,issue_reporter,summary, description="",watchers=[],links=[], buid="",wcid="",old_buid="",old_wcid="", indexing_type="",comments=[], issuetype="Task"): """ Creates a new issue with the given parameters. Inputs: *REQUIRED* :project: the jira project key in which to create the issue :issue_assignee: user name who the issue will be assigned to :issue_reporter: user name of the issue report :summary: string value of the issue summary field *OPTIONAL* :description: Issue description. Defaults to empty string :watchers: list of user names to add as issue watchers :link: list of issue keys to link to the issue as "Related To" :issuetype: the type of issue to create. Defaults to type. :buid: business unit - custom field 10502 :wcid: wrapping company id - custom field 10501 :old_buid: old business unit - custom field 13100 :old_wcid: old wrapping company id - custom field 13101 :indexing_type: the indexing type - custom field 10500 :comments: list dictionaries of comments and authors to auto add. Returns: Jira Issue Object """ issue_dict = { 'project':{'key':project}, 'summary': summary, 'issuetype': {'name':issuetype}, 'description':description, } new_issue = self.jira.create_issue(fields=issue_dict) # assign the audit tick to auditor new_issue.update(assignee={'name':issue_assignee}) new_issue.update(reporter={'name':issue_reporter}) # add watchers to audit ticket (reporter, assignee, wacthers from indexrep ticket) for watcher in watchers: try: self.jira.add_watcher(new_issue,watcher) except: pass # link the audit ticket back to indexrep ticket for link in links: self.jira.create_issue_link('Relates',new_issue,link) # add custom field values if set if buid: new_issue.update(fields={'customfield_10502':buid}) if wcid: new_issue.update(fields={'customfield_10501':wcid}) if indexing_type: new_issue.update(fields={'customfield_10500':{'value':indexing_type.value}}) if old_buid: new_issue.update(fields={'customfield_13100':old_buid}) if old_wcid: new_issue.update(fields={'customfield_13101':old_wcid}) # add comments quoted_comments = "" for comment in comments: quoted_comments = "%s[~%s] Said:{quote}%s{quote}\\\ \\\ " % (quoted_comments,comment['author'],comment['body']) if quoted_comments: quoted_comments = "Comments from the parent issue:\\\ %s" % quoted_comments self.jira.add_comment(new_issue,quoted_comments) return new_issue # method to transistion audit ticket def get_group_members(self, group_name): """ Returns the members of a group as a list """ group = self.jira.groups(query=group_name)[0] members = self.jira.group_members(group) return members def auto_assign(self,project="INDEXREP"): """ Looks up new INDEXREP issues with an empty assignee and non-agent reporter and assigns them to the user in the content-acquisition user group with the fewest assigned contect-acquistion tickets. """ # get member INDEXREP issues that need to auto assigned mem_issues = self.get_issues("member_auto_assign") # get free indexing requests free_issues = self.get_issues("free_auto_assign") # get unassigned member engagement issues mer_issues = self.get_issues("mer_auto_assign") # get unassigned sales engineering issues se_issues = self.get_issues("se_auto_assign") # get non-resolved assigned Member issues member_assigned_issues_query = self.get_issues("member_assigned_issues",True) # get non-resolved assigned Free Indexing issues free_assigned_issues_query = self.get_issues("free_assigned_issues",True) # get non-resolved member enagement issues mer_assigned_issues_query = self.get_issues("mer_assigned_issues",True) # get non-resolved sales engineering issues se_assigned_issues_query = self.get_issues("se_assigned_issues",True) def _assign(issue,username): """ Private method for assigning an issue. Inputs: issue: issue to assign username: person to assign the issue """ reporter = issue.fields.reporter.key self.jira.assign_issue(issue=issue,assignee=username) message = ("[~%s], this issue has been automically assigned " "to [~%s].") % (reporter,username) self.jira.add_comment(issue.key, message) auto_assign_dicts = [ { "issue_list": mem_issues, "assigned_list": member_assigned_issues_query, "assignee_group": "content-acquisition", }, { "issue_list": free_issues, "assigned_list": free_assigned_issues_query, "assignee_group": "content-acquisition-free", }, { "issue_list": mer_issues, "assigned_list": mer_assigned_issues_query, "assignee_group": "mer-assignees", "watch_list":"mer-auto-watch", }, { "issue_list": se_issues, "assigned_list": se_assigned_issues_query, "assignee_group": "se-assignees", }] for auto_assign_dict in auto_assign_dicts: for issue in auto_assign_dict["issue_list"]: username = self.user_with_fewest_issues(auto_assign_dict["assignee_group"], auto_assign_dict["assigned_list"]) if (auto_assign_dict["issue_list"]==mem_issues or auto_assign_dict["issue_list"]==free_issues): # check if the indexing type is already set. If so, do nothing. if issue.fields.customfield_10500 == None: # default to member indexing for issues in mem_issues if auto_assign_dict["issue_list"]==mem_issues: issue.update({"customfield_10500":{"id":"10103"}}) elif auto_assign_dict["issue_list"]==free_issues: free_index_mem = self.get_group_members("free-index-default") # set the indexing type to free if the reporter is in the list # of users who default to free if issue.fields.reporter.key in free_index_mem: issue.update({"customfield_10500":{"id":"10100"}}) #free else: #default is member otherwise issue.update({"customfield_10500":{"id":"10103"}}) # if the dict object has a watch list item, add default watchers if "watch_list" in auto_assign_dict: watchers = self.get_group_members(auto_assign_dict["watch_list"]) self.toggle_watchers("add",issue,watchers) _assign(issue,username) def remind_reporter_to_close(self): """ Comments on all non-closed resolved issues that are 13 days without a change. Notifies the reporter it will be closed in 24 hours and adds a label to the issue that is used as a lookup key by the close method. """ issues = self.get_issues("remind_close_issues") for issue in issues: reporter = issue.fields.reporter.key message = ( "[~%s], this issue has been resolved for 13 days. It will be " "closed automatically in 24 hours.") % reporter watch_list = self.toggle_watchers("remove",issue) self.jira.add_comment(issue.key,message) issue.fields.labels.append(self.ac_label) issue.update(fields={"labels": issue.fields.labels}) self.toggle_watchers("add",issue, watch_list) def close_resolved(self): """ Looks up all issues labeled for auto-closing that have not been updated in 24 hours and closes them. Ignores INDEXREP so as to not interfere with the auditing process. """ issues = self.get_issues("auto_close_issues") for issue in issues: reporter = issue.fields.reporter.key message = ( "[~%s], this issue has closed automatically.") % reporter close_me = self.close_issue(issue) self.jira.add_comment(issue.key,message) def get_transition_id(self,issue,key): """ Finds the transition id for an issue given a specific search string. Inputs: key: search string issue: jira issue Returns: transition id or False """ trans = self.jira.transitions(issue) tran_id = False for tran in trans: tran_name = tran['name'].lower() if key in tran_name: tran_id = tran['id'] return tran_id def close_issue(self, issue): """ Closes the issue passed to it with a resolution of fixed. Inputs: Issue: the issue object to close Returns: True|False """ trans = self.jira.transitions(issue) success_flag = False tran_id = self.get_transition_id(issue,"close") if not tran_id: tran_id = self.get_transition_id(issue,"complete") if tran_id: try: self.jira.transition_issue(issue,tran_id, {'resolution':{'id':'1'}}) #some close transitions don't have a resolution screen except: #open ended, but the JIRAError exception is broken. self.jira.transition_issue(issue,tran_id) success_flag = True return success_flag def clear_auto_close_label(self): """ Clears the auto-close label from issues that have been re-opened since the auto-close reminder was posted. """ issues = self.jira.search_issues( 'status in ("Quality Control", Reopened, Merged, open) \ AND labels in (auto-close-24-hours)') for issue in issues: label_list = issue.fields.labels watch_list = self.toggle_watchers("remove",issue) label_list.remove(self.ac_label) issue.update(fields={"labels": label_list}) self.toggle_watchers("add",issue, watch_list) def toggle_watchers(self,action,issue,watch_list=[]): """ Internal method that either adds or removes the watchers of an issue. If it removes them,it returns a list of users that were removed. If it adds, it returns an updated list of watchers. Inputs: :action: String "add"|"remove". The action to take :issue: Issue whose watchers list is being modified :watch_list: list of users. Optional for remove. Required for add. Returns: :issue_watcher: List of users who are or were watching the issue. """ if action=="remove": issue_watchers = self.jira.watchers(issue).watchers for issue_watcher in issue_watchers: # watch list can be inconsensent when returned by the jira api # same issue in the add loop try: self.jira.remove_watcher(issue,issue_watcher.name) except AttributeError: self.jira.add_watcher(issue,old_watcher) else: for old_watcher in watch_list: try: self.jira.add_watcher(issue,old_watcher.name) except AttributeError: self.jira.add_watcher(issue,old_watcher) issue_watchers = self.jira.watchers(issue).watchers return issue_watchers def label_contains(self,issue,search_string): """ Internal method that searches the labels of an issue for a given string value. It allows filtering that is roughly "labels ~ 'string'", which is not supported by JQL. Inputs: :issue: Jira issue object that is being checked :search_string: the string value being checked for Returns: True|False True if search_string exists in any label. """ return any(search_string in label for label in issue.fields.labels) def check_for_text(self,issue,text_list): """ Internal method that searches the summary and description of an issue for a given list of strings. Match is non-case sensative, and converts everything to lower case before checking for a match. Inputs: :issue: Jira issue object that is being checked :text_list: strings to checked for Returns: True|False True if any of the values in text_list exist. """ string_exists = False if issue.fields.summary: summary = issue.fields.summary.lower() else: summary = "" if issue.fields.description: description = issue.fields.description.lower() else: description = "" for text in text_list: text = text.lower() if text in summary or text in description: string_exists = True return string_exists def user_with_fewest_issues(self,group,query,blacklist=[]): """ Given a query, return the username of the use with the fewest assigned issues in the result set. Inputs: group: the group of users for which to count issues. query: the issues to lookup. Should be a JQL string. blacklist: (optional) list of inelligible users """ members = self.get_group_members(group) issues = self.jira.search_issues(query,maxResults=1000) member_count = {} for member in members: member_count[member]=0 # perform the count anew for each ticket for issue in issues: if issue.fields.assignee: assignee = issue.fields.assignee.key else: assignee = None if assignee in members and not self.label_contains(issue,"wait"): member_count[assignee] = member_count[assignee]+1 #sort the list so that the user with the lowest count is first member_count_sorted = sorted(member_count.items(), key=operator.itemgetter(1)) # prevent assignment to a user in the blacklist, so long as there are # at least 2 available users while (str(member_count_sorted[0][0]) in blacklist and len(member_count_sorted)>1): del member_count_sorted[0] # return the username of the user return str(member_count_sorted[0][0]) def get_issues(self,filter_key,return_jql=False): """ Returns issues found using a jira filter. Inputs: :filter_key: the dict key for the filter in settings :return_jql: flag to return JQL instead on issues Returns: :issues: Jira Issues object (default) or JQL string """ filter_id = secrets.jira_filters[filter_key] jql_query = self.jira.filter(filter_id).jql if return_jql: # some functionality needs the JQL instead of an issue list # notably the method self.user_with_fewest_issues return jql_query else: issues = self.jira.search_issues(jql_query) return issues
# pprint.pprint(issue_dict) issues = jira.create_issue(fields=issue_dict) # issues=str(issues) # cursor.execute('''update ARC_OrderFormValues_old07072017 # set jira_upload=? where ID=?''',str(issues),id1) # cnxn.commit() # cursor.execute(u,(issues,id1)) issues.update(timetracking={ 'originalEstimate': estimate }) #this is the only way originalEstimate can be updated. # print (issues.raw) # pprint.pprint(issues.raw) #this is too many #add orignalestimate to dict #add wa # issues.update(fields={'timetracking': 'new summary', 'description': 'A new summary was added'}) # # for i in issues: # # print (i) # #add the person in the email to watcher here. # jira.add_watcher(issues.id, 'siddhesh.narkar') jira.add_watcher(issues.id, 'dinesh.jayapathy') jira.add_comment(issues.id, 'CR Created') # issue_dict.update({'watcher': watcher, 'originalEstimate': estimate,'comment':issues.fields.comment['comments']}) pprint.pprint(issue_dict)
class Housekeeping(): """ This class is the container for all automated Jira functions performed by the Housekeeping agent. """ def __init__(self): # class variables self.ac_label = u'auto-close-24-hours' self.audit_delay = '-72h' self.audit_projects = "INDEXREP" #comma delimited project keys # open JIRA API Connection self.jira = JIRA(options=secrets.options, basic_auth=secrets.housekeeping_auth) # commands to run self.content_acquisition_auto_qc() self.auto_assign() self.remind_reporter_to_close() self.close_resolved() self.clear_auto_close_label() self.resolved_issue_audit() self.handle_audited_tickets() def content_acquisition_auto_qc(self): """ Takes INDEXREP issues that have been Merged for 30+ minutes and transitions them to Quality Control. It then adds a comment that tags the reporter to inform them that the issue is ready for review. """ issues = self.jira.search_issues( 'project=INDEXREP and status=Merged and updated<="-30m"') for issue in issues: reporter = issue.fields.reporter.key message = '[~%s], this issue is ready for QC.' % reporter """ 771 is the transition ID spedific to this step for this project. Anything more generic will need to parse the transitions list. """ self.jira.transition_issue(issue.key,'771') self.jira.add_comment(issue.key, message) def handle_audited_tickets(self): """ Handles audit tickets that are failed. Closed tickets are ignored. Failed tickets trigger the creation of a new ticket in the same project as the original ticket. Inputs: None Returns: None """ issues = self.jira.search_issues( # get all the ADT issues 'project=ADT and status="Failed Audit"') # For each failed issue, generate a new work ticket then close this one for issue in issues: #BUID adt_buid=issue.fields.customfield_10502 #WCID adt_wcid=issue.fields.customfield_10501 #Indexing Type adt_indexing_type=issue.fields.customfield_10500 #comments adt_comments = [] for comment in self.jira.comments(issue): node = { 'body':self.jira.comment(issue,comment).body, 'author': self.jira.comment(issue,comment).author.key } adt_comments.append(node) link_list = [issue.key,] # first linked ticket should be this audit ticket for link in issue.fields.issuelinks: # grab the rest of links try: link_list.append(link.outwardIssue.key) except AttributeError: pass # capture orignal tick and project original_ticket = issue.fields.summary.split("[")[1].split("]")[0] original_project = original_ticket.split("-")[0] # build the new summary by parsing the audit summary indexrep_summary = issue.fields.summary #build the summary indexrep_summary = indexrep_summary.replace("compliance audit - ","") indexrep_summary = indexrep_summary.split("[")[0] indexrep_summary = ' %s - Failed Audit' % (indexrep_summary) # Build the issue description message = 'This issue failed audit. Please review %s and make any \ necessary corrections.' % original_ticket # Construct the watcher list and de-dupe it watcher_list = [issue.fields.assignee.key,] for w in self.jira.watchers(issue).watchers: watcher_list.append(w.key) watcher_list = set(watcher_list) # get the reporter (reporter is preserved from audit to issue) reporter = issue.fields.reporter.key # Generate the new issue, then close the audit ticket. new_issue = self.make_new_issue(original_project,"EMPTY",reporter, indexrep_summary,message, watcher_list,link_list,adt_buid, adt_wcid,adt_indexing_type,adt_comments) close_me = self.close_issue(issue.key) def resolved_issue_audit(self,delay="",projects=""): """ TAKES issues that have been resolved from specified projectsfor a set interval and creates a new ticket in AUDIT, closes the INDEXREP ticket, and then assigns it to the audit user specified in the self.qa_auditor role. Inputs: :delay: how long an issue should be resoved before being picked up by this script. Defaults to class level variable :projects: which projects are subject to auditing. Defaults to class level variable Returns: Error message or Nothing """ delay = self.audit_delay if not delay else delay projects = self.audit_projects if not projects else projects # get all the issues from projects in the audit list issue_query = 'project in (%s) and status=Resolved and resolutiondate \ <="%s"' % (projects,delay) issues = self.jira.search_issues(issue_query) # get the users who can be assigned audit tickets. This should be just one person qa_members = self.get_group_members("issue audits") if len(qa_members)==1: qa_auditor=qa_members.keys()[0] else: # for now, throw an error. Later, assign to user with fewer ADT tickets # this will also mean turning the code in auto_assign into a method (DRY) return "Error: There is more than one possible auditor" # cycle through them and create a new ADT ticket for each for issue in issues: #BUID ind_buid=issue.fields.customfield_10502 #WCID ind_wcid=issue.fields.customfield_10501 #Indexing Type ind_indexing_type=issue.fields.customfield_10500 link_list = [issue.key,] for link in issue.fields.issuelinks: # grab the rest of links try: link_list.append(link.outwardIssue.key) except AttributeError: pass # build the new ticket summary based on the issue being audited # [ISSUE=123] is used to preserve the original issue key. Replace any brackets with () # to prevent read errors later. adt_summary = issue.fields.summary.replace("[","(").replace("]",")") adt_summary = 'compliance audit - %s [%s]' % (adt_summary,issue.key) # build the description message = '[~%s], issue %s is ready to audit.' % (qa_auditor, issue.key) #build the watcher list, including original reporter and assignee of the audited ticket watcher_list = [] for w in self.jira.watchers(issue).watchers: watcher_list.append(w.key) reporter = issue.fields.reporter.key try: original_assignee = issue.fields.assignee.key except AttributeError: original_assignee="EMPTY" # make the audit ticket new_issue = self.make_new_issue("ADT",qa_auditor,reporter, adt_summary,message,watcher_list,link_list,ind_buid, ind_wcid,ind_indexing_type) # close the INDEXREP ticket close_me = self.close_issue(issue.key) # add comment to indexrep ticket link_back_comment = "This issue has been closed. The audit ticket is %s" % new_issue self.jira.add_comment(issue.key, link_back_comment) def make_new_issue(self,project,issue_assignee,issue_reporter,summary, description="",watchers=[],links=[],buid="",wcid="", indexing_type="",comments=[],issuetype="Task"): """ Creates a new issue with the given parameters. Inputs: *REQUIRED* :project: the jira project key in which to create the issue :issue_assignee: user name who the issue will be assigned to :issue_reporter: user name of the issue report :summary: string value of the issue summary field *OPTIONAL* :description: Issue description. Defaults to empty string :watchers: list of user names to add as issue watchers :link: list of issue keys to link to the issue as "Related To" :issuetype: the type of issue to create. Defaults to type. :buid: business unit - custom field 10502 :wcid: wrapping company id - custom field 10501 :indexing_type: the indexing type - custom field 10500 :comments: list dictionaries of comments and authors to auto add. Returns: Jira Issue Object """ issue_dict = { 'project':{'key':project}, 'summary': summary, 'issuetype': {'name':issuetype}, 'description':description, } new_issue = self.jira.create_issue(fields=issue_dict) # assign the audit tick to auditor new_issue.update(assignee={'name':issue_assignee}) new_issue.update(reporter={'name':issue_reporter}) # add watchers to audit ticket (reporter, assignee, wacthers from indexrep ticket) for watcher in watchers: self.jira.add_watcher(new_issue,watcher) # link the audit ticket back to indexrep ticket for link in links: self.jira.create_issue_link('Relates',new_issue,link) # add custom field values if set if buid: new_issue.update(fields={'customfield_10502':buid}) if wcid: new_issue.update(fields={'customfield_10501':wcid}) if indexing_type: new_issue.update(fields={'customfield_10500':{'value':indexing_type.value}}) # add comments quoted_comments = "" for comment in comments: quoted_comments = "%s[~%s] Said:{quote}%s{quote}\\\ \\\ " % (quoted_comments,comment['author'],comment['body']) if quoted_comments: quoted_comments = "Comments from the parent issue:\\\ %s" % quoted_comments self.jira.add_comment(new_issue,quoted_comments) return new_issue # method to transistion audit ticket def get_group_members(self, group_name): """ Returns the members of a """ group = self.jira.groups( query=group_name )['groups'][0]['name'] members = self.jira.group_members(group) return members def auto_assign(self): """ Looks up new INDEXREP issues with an empty assignee and non-agent reporter and assigns them to the user in the content-acquisition user group with the fewest assigned contect-acquistion tickets. """ # filter 20702 returns issues that need to auto assigned jql_query = self.jira.filter("20702").jql issues = self.jira.search_issues(jql_query) #filter 21200 returns non-resolved assigned issues assigned_issues_query = self.jira.filter("21200").jql # cycle through each issue and assign it to the user in # content acquisition with the fewest assigned tickets for issue in issues: username = self.user_with_fewest_issues('content-acquisition', assigned_issues_query) reporter = issue.fields.reporter.key watch_list = self.toggle_watchers("remove",issue) self.jira.assign_issue(issue=issue,assignee=username) message = ("[~%s], this issue has been automically assigned " "to [~%s].") % (reporter,username) self.jira.add_comment(issue.key, message) self.toggle_watchers("add",issue,watch_list) def remind_reporter_to_close(self): """ Comments on all non-closed resolved issues that are 13 days without a change. Notifies the reporter it will be closed in 24 hours and adds a label to the issue that is used as a lookup key by the close method. """ issues = self.jira.search_issues( 'resolution != EMPTY AND \ status not in (closed, "Quality Control", Reopened, Merged, open) \ AND updated <= -13d and project not in (INDEXREP)') for issue in issues: reporter = issue.fields.reporter.key message = ( "[~%s], this issue has been resolved for 13 days. It will be " "closed automatically in 24 hours.") % reporter watch_list = self.toggle_watchers("remove",issue) self.jira.add_comment(issue.key,message) issue.fields.labels.append(self.ac_label) issue.update(fields={"labels": issue.fields.labels}) self.toggle_watchers("add",issue, watch_list) def close_resolved(self): """ Looks up all issues labeled for auto-closing that have not been updated in 24 hours and closes them. Ignores INDEXREP so as to not interfere with the auditing process. """ issues = self.jira.search_issues( 'resolution != EMPTY AND \ status not in (closed, "Quality Control", Reopened, Merged, open, \ passed,staged) AND project not in (INDEXREP) \ AND updated <= -24h \ AND labels in (auto-close-24-hours)') for issue in issues: reporter = issue.fields.reporter.key message = ( "[~%s], this issue has closed automatically.") % reporter close_me = self.close_issue(issue) self.jira.add_comment(issue.key,message) def close_issue(self, issue): """ Closes the issue passed to it with a resolution of fixed. Inputs: Issue: the issue object to close Returns: True|False """ trans = self.jira.transitions(issue) success_flag = False for tran in trans: tran_name = tran['name'].lower() if 'close' in tran_name or 'complete' in tran_name: try: self.jira.transition_issue(issue,tran['id'],{'resolution':{'id':'1'}}) #some close transitions don't have a resolution screen except: #open ended, but the JIRAError exception is broken. self.jira.transition_issue(issue,tran['id']) success_flag = True return success_flag def clear_auto_close_label(self): """ Clears the auto-close label from issues that have been re-opened since the auto-close reminder was posted. """ issues = self.jira.search_issues( 'status in ("Quality Control", Reopened, Merged, open) \ AND labels in (auto-close-24-hours)') for issue in issues: label_list = issue.fields.labels watch_list = self.toggle_watchers("remove",issue) label_list.remove(self.ac_label) issue.update(fields={"labels": label_list}) self.toggle_watchers("add",issue, watch_list) def toggle_watchers(self,action,issue,watch_list=[]): """ Internal method that either adds or removes the watchers of an issue. If it removes them,it returns a list of users that were removed. If it adds, it returns an updated list of watchers. Inputs: :action: String "add"|"remove". The action to take :issue: Issue whose watchers list is being modified :watch_list: list of users. Optional for remove. Required for add. Returns: :issue_watcher: List of users who are or were watching the issue. """ if action=="remove": issue_watchers = self.jira.watchers(issue).watchers for issue_watcher in issue_watchers: self.jira.remove_watcher(issue,issue_watcher.name) else: for old_watcher in watch_list: self.jira.add_watcher(issue,old_watcher.name) issue_watchers = self.jira.watchers(issue).watchers return issue_watchers def label_contains(self,issue,search_string): """ Internal method that searches the labels of an issue for a given string value. It allows filtering that is roughly "labels ~ 'string'", which is not supported by JQL. Inputs: :issue: Jira issue object that is being checked :search_string: the string value being checked for Returns: True|False True if search_string exists in any label. """ return any(search_string in label for label in issue.fields.labels) def user_with_fewest_issues(self,group,query): """ Given a query, return the username of the use with the fewest assigned issues in the result set. Inputs: group: the group of users for which to count issues. query: the issues to lookup. Should be a JQL string. """ members = self.get_group_members(group) issues = self.jira.search_issues(query) member_count = {} for member in members: member_count[member]=0 # perform the count anew for each ticket for issue in issues: if issue.fields.assignee: assignee = issue.fields.assignee.key else: assignee = None if assignee in members and not self.label_contains(issue,"wait"): member_count[assignee] = member_count[assignee]+1 #sort the list so that the user with the lowest count is first member_count_sorted = sorted(member_count.items(), key=operator.itemgetter(1)) # return the username of the user return str(member_count_sorted[0][0])
class Housekeeping(): """ This class is the container for all automated Jira functions performed by the Housekeeping agent. """ def __init__(self): # class variables self.ac_label = u'auto-close-24-hours' self.audit_delay = '-72h' self.audit_projects = "INDEXREP" #comma delimited project keys # open JIRA API Connection self.jira = JIRA(options=secrets.options, basic_auth=secrets.housekeeping_auth) # commands to run self.content_acquisition_auto_qc() self.auto_assign() self.remind_reporter_to_close() self.close_resolved() self.clear_auto_close_label() self.resolved_issue_audit() self.handle_audited_tickets() def content_acquisition_auto_qc(self): """ Takes INDEXREP issues that have been Merged for 30+ minutes and transitions them to Quality Control. It then adds a comment that tags the reporter to inform them that the issue is ready for review. """ issues = self.jira.search_issues( 'project=INDEXREP and status=Merged and updated<="-30m"') for issue in issues: reporter = issue.fields.reporter.key message = '[~%s], this issue is ready for QC.' % reporter """ 771 is the transition ID spedific to this step for this project. Anything more generic will need to parse the transitions list. """ self.jira.transition_issue(issue.key, '771') self.jira.add_comment(issue.key, message) def handle_audited_tickets(self): """ Handles audit tickets that are failed. Closed tickets are ignored. Failed tickets trigger the creation of a new ticket in the same project as the original ticket. Inputs: None Returns: None """ issues = self.jira.search_issues( # get all the ADT issues 'project=ADT and status="Failed Audit"') # For each failed issue, generate a new work ticket then close this one for issue in issues: #BUID adt_buid = issue.fields.customfield_10502 #WCID adt_wcid = issue.fields.customfield_10501 #Indexing Type adt_indexing_type = issue.fields.customfield_10500 #comments adt_comments = [] for comment in self.jira.comments(issue): node = { 'body': self.jira.comment(issue, comment).body, 'author': self.jira.comment(issue, comment).author.key } adt_comments.append(node) link_list = [ issue.key, ] # first linked ticket should be this audit ticket for link in issue.fields.issuelinks: # grab the rest of links try: link_list.append(link.outwardIssue.key) except AttributeError: pass # capture orignal tick and project original_ticket = issue.fields.summary.split("[")[1].split("]")[0] original_project = original_ticket.split("-")[0] # build the new summary by parsing the audit summary indexrep_summary = issue.fields.summary #build the summary indexrep_summary = indexrep_summary.replace( "compliance audit - ", "") indexrep_summary = indexrep_summary.split("[")[0] indexrep_summary = ' %s - Failed Audit' % (indexrep_summary) # Build the issue description message = 'This issue failed audit. Please review %s and make any \ necessary corrections.' % original_ticket # Construct the watcher list and de-dupe it watcher_list = [ issue.fields.assignee.key, ] for w in self.jira.watchers(issue).watchers: watcher_list.append(w.key) watcher_list = set(watcher_list) # get the reporter (reporter is preserved from audit to issue) reporter = issue.fields.reporter.key # Generate the new issue, then close the audit ticket. new_issue = self.make_new_issue(original_project, "EMPTY", reporter, indexrep_summary, message, watcher_list, link_list, adt_buid, adt_wcid, adt_indexing_type, adt_comments) close_me = self.close_issue(issue.key) def resolved_issue_audit(self, delay="", projects=""): """ TAKES issues that have been resolved from specified projectsfor a set interval and creates a new ticket in AUDIT, closes the INDEXREP ticket, and then assigns it to the audit user specified in the self.qa_auditor role. Inputs: :delay: how long an issue should be resoved before being picked up by this script. Defaults to class level variable :projects: which projects are subject to auditing. Defaults to class level variable Returns: Error message or Nothing """ delay = self.audit_delay if not delay else delay projects = self.audit_projects if not projects else projects # get all the issues from projects in the audit list issue_query = 'project in (%s) and status=Resolved and resolutiondate \ <="%s"' % (projects, delay) issues = self.jira.search_issues(issue_query) # get the users who can be assigned audit tickets. This should be just one person qa_members = self.get_group_members("issue audits") if len(qa_members) == 1: qa_auditor = qa_members.keys()[0] else: # for now, throw an error. Later, assign to user with fewer ADT tickets # this will also mean turning the code in auto_assign into a method (DRY) return "Error: There is more than one possible auditor" # cycle through them and create a new ADT ticket for each for issue in issues: #BUID ind_buid = issue.fields.customfield_10502 #WCID ind_wcid = issue.fields.customfield_10501 #Indexing Type ind_indexing_type = issue.fields.customfield_10500 link_list = [ issue.key, ] for link in issue.fields.issuelinks: # grab the rest of links try: link_list.append(link.outwardIssue.key) except AttributeError: pass # build the new ticket summary based on the issue being audited # [ISSUE=123] is used to preserve the original issue key. Replace any brackets with () # to prevent read errors later. adt_summary = issue.fields.summary.replace("[", "(").replace("]", ")") adt_summary = 'compliance audit - %s [%s]' % (adt_summary, issue.key) # build the description message = '[~%s], issue %s is ready to audit.' % (qa_auditor, issue.key) #build the watcher list, including original reporter and assignee of the audited ticket watcher_list = [] for w in self.jira.watchers(issue).watchers: watcher_list.append(w.key) reporter = issue.fields.reporter.key try: original_assignee = issue.fields.assignee.key except AttributeError: original_assignee = "EMPTY" # make the audit ticket new_issue = self.make_new_issue("ADT", qa_auditor, reporter, adt_summary, message, watcher_list, link_list, ind_buid, ind_wcid, ind_indexing_type) # close the INDEXREP ticket close_me = self.close_issue(issue.key) # add comment to indexrep ticket link_back_comment = "This issue has been closed. The audit ticket is %s" % new_issue self.jira.add_comment(issue.key, link_back_comment) def make_new_issue(self, project, issue_assignee, issue_reporter, summary, description="", watchers=[], links=[], buid="", wcid="", indexing_type="", comments=[], issuetype="Task"): """ Creates a new issue with the given parameters. Inputs: *REQUIRED* :project: the jira project key in which to create the issue :issue_assignee: user name who the issue will be assigned to :issue_reporter: user name of the issue report :summary: string value of the issue summary field *OPTIONAL* :description: Issue description. Defaults to empty string :watchers: list of user names to add as issue watchers :link: list of issue keys to link to the issue as "Related To" :issuetype: the type of issue to create. Defaults to type. :buid: business unit - custom field 10502 :wcid: wrapping company id - custom field 10501 :indexing_type: the indexing type - custom field 10500 :comments: list dictionaries of comments and authors to auto add. Returns: Jira Issue Object """ issue_dict = { 'project': { 'key': project }, 'summary': summary, 'issuetype': { 'name': issuetype }, 'description': description, } new_issue = self.jira.create_issue(fields=issue_dict) # assign the audit tick to auditor new_issue.update(assignee={'name': issue_assignee}) new_issue.update(reporter={'name': issue_reporter}) # add watchers to audit ticket (reporter, assignee, wacthers from indexrep ticket) for watcher in watchers: self.jira.add_watcher(new_issue, watcher) # link the audit ticket back to indexrep ticket for link in links: self.jira.create_issue_link('Relates', new_issue, link) # add custom field values if set if buid: new_issue.update(fields={'customfield_10502': buid}) if wcid: new_issue.update(fields={'customfield_10501': wcid}) if indexing_type: new_issue.update( fields={'customfield_10500': { 'value': indexing_type.value }}) # add comments quoted_comments = "" for comment in comments: quoted_comments = "%s[~%s] Said:{quote}%s{quote}\\\ \\\ " % ( quoted_comments, comment['author'], comment['body']) if quoted_comments: quoted_comments = "Comments from the parent issue:\\\ %s" % quoted_comments self.jira.add_comment(new_issue, quoted_comments) return new_issue # method to transistion audit ticket def get_group_members(self, group_name): """ Returns the members of a """ group = self.jira.groups(query=group_name)['groups'][0]['name'] members = self.jira.group_members(group) return members def auto_assign(self): """ Looks up new INDEXREP issues with an empty assignee and non-agent reporter and assigns them to the user in the content-acquisition user group with the fewest assigned contect-acquistion tickets. """ # filter 20702 returns issues that need to auto assigned jql_query = self.jira.filter("20702").jql issues = self.jira.search_issues(jql_query) #filter 21200 returns non-resolved assigned issues assigned_issues_query = self.jira.filter("21200").jql # cycle through each issue and assign it to the user in # content acquisition with the fewest assigned tickets for issue in issues: username = self.user_with_fewest_issues('content-acquisition', assigned_issues_query) reporter = issue.fields.reporter.key watch_list = self.toggle_watchers("remove", issue) self.jira.assign_issue(issue=issue, assignee=username) message = ("[~%s], this issue has been automically assigned " "to [~%s].") % (reporter, username) self.jira.add_comment(issue.key, message) self.toggle_watchers("add", issue, watch_list) def remind_reporter_to_close(self): """ Comments on all non-closed resolved issues that are 13 days without a change. Notifies the reporter it will be closed in 24 hours and adds a label to the issue that is used as a lookup key by the close method. """ issues = self.jira.search_issues('resolution != EMPTY AND \ status not in (closed, "Quality Control", Reopened, Merged, open) \ AND updated <= -13d and project not in (INDEXREP)') for issue in issues: reporter = issue.fields.reporter.key message = ( "[~%s], this issue has been resolved for 13 days. It will be " "closed automatically in 24 hours.") % reporter watch_list = self.toggle_watchers("remove", issue) self.jira.add_comment(issue.key, message) issue.fields.labels.append(self.ac_label) issue.update(fields={"labels": issue.fields.labels}) self.toggle_watchers("add", issue, watch_list) def close_resolved(self): """ Looks up all issues labeled for auto-closing that have not been updated in 24 hours and closes them. Ignores INDEXREP so as to not interfere with the auditing process. """ issues = self.jira.search_issues('resolution != EMPTY AND \ status not in (closed, "Quality Control", Reopened, Merged, open, \ passed,staged) AND project not in (INDEXREP) \ AND updated <= -24h \ AND labels in (auto-close-24-hours)') for issue in issues: reporter = issue.fields.reporter.key message = ( "[~%s], this issue has closed automatically.") % reporter close_me = self.close_issue(issue) self.jira.add_comment(issue.key, message) def close_issue(self, issue): """ Closes the issue passed to it with a resolution of fixed. Inputs: Issue: the issue object to close Returns: True|False """ trans = self.jira.transitions(issue) success_flag = False for tran in trans: tran_name = tran['name'].lower() if 'close' in tran_name or 'complete' in tran_name: try: self.jira.transition_issue(issue, tran['id'], {'resolution': { 'id': '1' }}) #some close transitions don't have a resolution screen except: #open ended, but the JIRAError exception is broken. self.jira.transition_issue(issue, tran['id']) success_flag = True return success_flag def clear_auto_close_label(self): """ Clears the auto-close label from issues that have been re-opened since the auto-close reminder was posted. """ issues = self.jira.search_issues( 'status in ("Quality Control", Reopened, Merged, open) \ AND labels in (auto-close-24-hours)') for issue in issues: label_list = issue.fields.labels watch_list = self.toggle_watchers("remove", issue) label_list.remove(self.ac_label) issue.update(fields={"labels": label_list}) self.toggle_watchers("add", issue, watch_list) def toggle_watchers(self, action, issue, watch_list=[]): """ Internal method that either adds or removes the watchers of an issue. If it removes them,it returns a list of users that were removed. If it adds, it returns an updated list of watchers. Inputs: :action: String "add"|"remove". The action to take :issue: Issue whose watchers list is being modified :watch_list: list of users. Optional for remove. Required for add. Returns: :issue_watcher: List of users who are or were watching the issue. """ if action == "remove": issue_watchers = self.jira.watchers(issue).watchers for issue_watcher in issue_watchers: self.jira.remove_watcher(issue, issue_watcher.name) else: for old_watcher in watch_list: self.jira.add_watcher(issue, old_watcher.name) issue_watchers = self.jira.watchers(issue).watchers return issue_watchers def label_contains(self, issue, search_string): """ Internal method that searches the labels of an issue for a given string value. It allows filtering that is roughly "labels ~ 'string'", which is not supported by JQL. Inputs: :issue: Jira issue object that is being checked :search_string: the string value being checked for Returns: True|False True if search_string exists in any label. """ return any(search_string in label for label in issue.fields.labels) def user_with_fewest_issues(self, group, query): """ Given a query, return the username of the use with the fewest assigned issues in the result set. Inputs: group: the group of users for which to count issues. query: the issues to lookup. Should be a JQL string. """ members = self.get_group_members(group) issues = self.jira.search_issues(query) member_count = {} for member in members: member_count[member] = 0 # perform the count anew for each ticket for issue in issues: if issue.fields.assignee: assignee = issue.fields.assignee.key else: assignee = None if assignee in members and not self.label_contains(issue, "wait"): member_count[assignee] = member_count[assignee] + 1 #sort the list so that the user with the lowest count is first member_count_sorted = sorted(member_count.items(), key=operator.itemgetter(1)) # return the username of the user return str(member_count_sorted[0][0])
class Jira(object): ''' Encapsulates interaction with Jira server. ''' def __init__(self, config): self.config = config self.options = {'server': config['server']} self.server = JIRA(self.options, basic_auth=(config['username'], config['password'])) def query(self, jql_query): ''' Run a JQL query. ''' return self.server.search_issues(jql_query) def create_issue(self, summary, description, kind='Bug'): ''' Create a Jira issue -- defaults to a bug. ''' new_issue = self.server.create_issue( project={'key': self.config['project']}, summary=summary, description=summary, # components=[{'id': '10301', 'name': 'Server Engineering'}], assignee={'name': self.config['username']}, issuetype={'name': kind}) return new_issue def close_issue(self, ticket): issue = self.get_issue(ticket) transitions = self.server.transitions(issue) transition_names = [x['name'] for x in transitions] if 'Stop Progress' in transition_names: self.transition_issue(ticket, status='Stop Progress') transitions = self.server.transitions(issue) transition_names = [x['name'] for x in transitions] if 'Resolve Issue' in transition_names: self.transition_issue(ticket, status='Resolve Issue') return self.get_issue(ticket) else: raise InvalidJiraStatusException( '\nattempt to close ticket failed. \ \nValid transitions: {0}'.format(', '.join(transition_names))) def transition_issue(self, ticket, status='Resolve Issue'): issue = self.get_issue(ticket) transitions = self.server.transitions(issue) transition_names = [x['name'] for x in transitions] if status not in transition_names: raise InvalidJiraStatusException( '\n\'{0}\' is not a valid status for this Jira issue. \ \nValid transitions: {1}'.format( status, ', '.join(transition_names))) _id = [x['id'] for x in transitions if x['name'] == status][0] # transition it: self.server.transition_issue(issue, _id) return self.get_issue(ticket) #users = self.jira.server.search_assignable_users_for_issues('', # project='CIGNAINC', maxResults=500) def add_watcher(self, ticket, person): issue = self.get_issue(ticket) self.server.add_watcher(issue, person) return issue def list_reviewable(self): ''' if a JQL query is defined in the config use it. If not: if a named filter is defined in the config use it. If not: use the predefined filter. ''' section = self.config.get('review', None) jql = 'status IN ("Ready for Review", "In Review") \ ORDER BY priority, updatedDate ASC' if not section: return self.query(jql) jql_config = section.get('jql', None) if jql_config: return self.query(jql_config) filter_name = section.get('filter', None) if filter_name: filters = self.server.favourite_filters() jql = [x.jql for x in filters if x.name == filter_name][0] return self.query(jql) return self.query(jql) def list_issues(self): ''' if a JQL query is defined in the config use it. If not: if a named filter is defined in the config use it. If not: use the predefined filter. ''' section = self.config.get('list', None) jql = 'assignee=currentUser() \ AND status != Closed AND status != Resolved' if not section: return self.query(jql) jql_config = section.get('jql', None) if jql_config: return self.query(jql_config) filter_name = section.get('filter', None) if filter_name: filters = self.server.favourite_filters() jql = [x.jql for x in filters if x.name == filter_name][0] return self.query(jql) return self.query(jql) def delete_issue(self, ticket): issue = self.get_issue(ticket) issue.delete() def assign_issue(self, ticket, assignee): ''' Given an issue and an assignee, assigns the issue to the assignee. ''' issue = self.get_issue(ticket) self.server.assign_issue(issue, assignee) return issue def add_comment(self, ticket, comment): ''' Given an issue and a comment, adds the comment to the issue. ''' issue = self.get_issue(ticket) self.server.add_comment(issue, comment) return issue def get_issue(self, ticket): ''' Given an issue name, returns a Jira instance of that issue. ''' if isinstance(ticket, Issue): ticket = ticket.key return self.server.issue('{0}'.format(ticket))
t2_fields = {"project": {'key': proj}, 'summary': t2_summary, 'description': t2_description, 'reporter': {'name': t2_reporter}, 'issuetype': {'name': t2_issuetype}, 'priority': {'id': t2_priority} } print "Creating ticket in project {0} with summary, description, issue type, priority, and reporter from {1}...".format(proj, t1.key), t2 = sup.create_issue(t2_fields) print "success (ticket {0} created)".format(t2.key) print "Adding watchers from {0} to {1}...".format(t1.key, t2.key), for w in sup.watchers(t1).watchers: sup.add_watcher(t2, w.name) print "success" print "Linking {0} to {1} with link type of {2}...".format(t1.key, t2.key, link_type), sup.create_issue_link(link_type, t1.key, t2.key) print "success" print "Adding closing comment to {0}...".format(t1.key), t1_comment = 'Closing this ticket, this issue can be further communicated in linked roadmap ticket {0}.\n\nThanks'.format(t2.key) sup.add_comment(t1.key, t1_comment) print "success" print "Silently closing {0}...".format(t1.key), sup.transition_issue(t1, getTransitionID(t1, 'Silently Close'), resolution={'id': getResolutionID('Transitioned to Roadmap')}) print "success"
fields_dict[k] = eval(v) except: continue jira_issue.update(fields=fields_dict) user = find_user(ticket_requester, config.getint('jira', 'find_user_algo_type_description'), config.get('jira', 'find_user_projects')) if user: # Make the ticket requester the reporter of the JIRA ticket. logger.debug('Making (' + user.name + ') the reporter of (' + jira_issue.key + ')') syslog.syslog(syslog.LOG_DEBUG, 'Making (' + user.name + ') the reporter of (' + jira_issue.key + ')') jira_issue.update(fields={'reporter':{'name': user.name}}) # Auto-add ticket requester as watcher to the JIRA ticket. logger.debug('Adding (' + user.name + ') as a watcher to (' + jira_issue.key + ')') syslog.syslog(syslog.LOG_DEBUG, 'Adding (' + user.name + ') as a watcher to (' + jira_issue.key + ')') jira.add_watcher(jira_issue, user.name) else: logger.warn('Unable to find equivalent RT requester in JIRA: ' + ticket_requester) syslog.syslog(syslog.LOG_WARNING, 'Unable to find equivalent RT requester in JIRA: ' + ticket_requester) # If global watchers are specified, then add them to the newly created ticket. create_watchers = config.get('jira', 'create_watchers') if create_watchers != "None": for create_watcher in create_watchers.split(','): logger.debug('Adding (' + create_watcher + ') as a watcher to (' + jira_issue.key + ')') syslog.syslog(syslog.LOG_DEBUG, 'Adding (' + create_watcher + ') as a watcher to (' + jira_issue.key + ')') jira.add_watcher(jira_issue, create_watcher) # Once the JIRA ticket is created, should a new label be assigned to the ticket? new_issue_label = config.get('jira', 'new_issue_label') if new_issue_label != "None":
class JiraRobot: ROBOT_LIBRARY_SCOPE = 'GLOBAL' jira = None def connect_to_jira(self, JIRAUsername=None, JIRAPassword=None, options=None): """ Connect to a JIRA server. Arguments: | JIRAUsername (string) | (Optional) A JIRA Username you wish to authenticate with, will authorise with anonymous account if left empty | | JIRAPassword (string) | (Optional) If a username was specified and a password is not the user will be prompted for password at runtime | | options (dictionary) | (Optional) A dictionary of options that the JIRA connection will be initialied with | This must be called first to be able to do most JIRA actions such as creating a new issue and assigning users. When connecting to JIRA you may need to authenticate a user. This can be done by passing in Username and Password as parameters. However, should you wish to not have a sensitive password saved in a file, an option is available to not pass in a Password and a prompt will appear asking for the JIRA password at runtime. 'connect to jira' can be called on its own and will default options to: '{'rest_api_version': '2', 'verify': True, 'server': 'http://*****:*****@ssword | {'server': http://devjira01'} | """ if JIRAUsername is not None and (JIRAPassword is "" or JIRAPassword is None): JIRAPassword = getpass.getpass("\nJIRA Password: "******"\nAuthentication to JIRA unsuccessful. Ensure the user used has sufficient access and that Username and Password were correct\n\n") sys.exit(1) else: try: self.jira = JIRA(options=JIRAOptions, basic_auth=(str(JIRAUsername), str(JIRAPassword))) except: sys.__stdout__.write("\nAuthentication to JIRA unsuccessful. Ensure the user used has sufficient access and that Username and Password were correct\n\n") sys.exit(1) def create_issue(self, issue_field_dict, assign_current_user=False): """ Creates a new JIRA issue. Arguments: | issue_field_dict (string) | A dictionary in the form of a string that the user can specify the issues fields and field values | | assign_current_user (string/bool) | (Optional) A flag to assign the current user to the issue once it is successfully created, defaults to False | Will create a new issue and returns an Issue Key. The user will use the issue_field_dict variable to specify the issues field and their respective values. This must be in the form of a string written as a dictionary e.g. {'FieldName':'FieldValue'} The field value can also be another dictionary (in some field cases this is needed) e.g. {'FieldName1':{'key':'value'}, 'FieldName2':FieldValue, 'FieldName3':{'name':'value'}} This Create Issue Example page (https://developer.atlassian.com/display/JIRADEV/JIRA+REST+API+Example+-+Create+Issue) is full of useful information in what is required for creating issues, including customfields and issue field value types, it is very important to get the value type right or an error will be thrown. Examples: | *Keyword* | *Parameters* | | | | ${issue_field_dict} | {'project':{'key': 'PROJ'}, 'summary':'Create New Issue', 'description':'Creating a new issue', 'issuetype':{'name': 'Bug'}} | | | connect to jira | asimmons | options= {'http://devjira01'} | | | ${issue} | create issue | ${issue_field_dict} | | | connect to jira | asimmons | options= {'http://devjira01'} | | | ${issue} | create issue | ${issue_field_dict} | True | """ issue_field_dict = eval(str(issue_field_dict)) print issue_field_dict new_issue = self.jira.create_issue(issue_field_dict) if assign_current_user is True: self.assign_user_to_issue(new_issue, self.jira.current_user()) return new_issue def create_issue_link(self, link_type, inwardissue, outwardissue, comment=None): """ Create a link between two issues. Arguments: | link_type (string) | The type of link | | inwardissue (string) | The issue to link from | | outwardissue (string) | The issue to link to | | comment (string) | (Optional) A comment to add when joining issues | Example: | *Keyword* | *Parameters* | | | | connect to jira | asimmons | options= {'http://devjira01'} | | | ${issue} | create issue | ${issue_field_dict} | True | | create issue link | relates to | ${issue} | PROJ-385 | """ self.jira.create_issue_link(type=link_type, inwardIssue=str(inwardissue), outwardIssue=str(outwardissue)) def assign_user_to_issue(self, issue, JIRAUsername): # TODO: Review docs """ Adds a user to a specified issue's watcher list Arguments: | issue (string) | A JIRA Issue that a user needs to be assigned to, can be an issue ID or Key | | JIRAUsername (string) | A JIRA Username to assign a user to an issue | Example: | *Keyword* | *Parameters* | | | connect to jira | asimmons | options= {'http://devjira01'} | | ${issue} | create issue | ${issue_field_dict} | | assign user to issue | ${issue} | aSample | """ self.jira.assign_issue(issue=issue, assignee=JIRAUsername) def get_current_user(self): """ Returns the current user used in the Connect to JIRA keyword. """ return self.jira.current_user() def add_watcher_to_issue(self, issue, JIRAUsername): """ Adds a user to a specified issue's watcher list. Arguments: | issue (string) | A JIRA Issue that a watcher needs added to, can be an issue ID or Key | | JIRAUsername (string) | A JIRA Username to add as a watcher to an issue | Example: | *Keyword* | *Parameters* | | | | connect to jira | asimmons | options= {'http://devjira01'} | | | ${issue} | create issue | ${issue_field_dict} | True | | add watcher to issue | ${issue} | aSample | | """ self.jira.add_watcher(issue=issue, watcher=JIRAUsername) def add_comment_to_issue(self, issue, comment, visibility=None): """ Adds a comment to a specified issue from the current user. Arguments: | issue (string) | A JIRA Issue that a watcher needs added to, can be an issue ID or Key | | comment (string) | A body of text to add as a comment to an issue | | visibility (string) | (Optional) | Example: | *Keyword* | *Parameters* | | | | connect to jira | asimmons | options= {'http://devjira01'} | | | ${issue} | create issue | ${issue_field_dict} | True | | add comment to issue | ${issue} | Starting work on this issue | | """ self.jira.add_comment(issue=issue, body=comment) def add_attachment_to_issue(self, issue, attachment, filename=None): """ Uploads and attaches a file a specified issue. (Note: include the file extention when using the 'filename' option or this will change the file type.) Arguments: | issue (string) | A JIRA Issue that a watcher needs added to, can be an issue ID or Key | | attachment (string) | A string pointing to a file location to upload and attach to the issue | | filename (string) | (Optional) A string to rename the file to upon attaching to the issue | Example: | *Keyword* | *Parameters* | | | | connect to jira | asimmons | options= {'http://devjira01'} | | | ${issue} | create issue | ${issue_field_dict} | True | | add attchment to issue | ${issue} | ./logfile.text | LogInformation.txt | """ self.jira.add_attachment(issue=issue, attachment=attachment, filename=filename)
class Jira(Config): def __init__(self, **kwargs): super(Jira, self).__init__() self.description = None self.issue_aus = None self.issue_us = None self.issue_uk = None self.issue_es = None self.issue_de = None self.issue_fr = None self.issue_apac = None self.issue_mea = None self.create_aus = None self.create_us = None self.create_apac = None self.create_de = None self.create_mea = None self.create_fr = None self.create_es = None self.create_uk = None self.options = {'server': self.section_value[4], 'verify': False} self.client = None if len(kwargs) != 2: raise JiraException( 'In order to use this class you need to specify a user and a password as keyword arguments!' ) else: if 'username' in kwargs.keys(): self.username = kwargs['username'] else: raise JiraException( 'You need to specify a username as keyword argument!') if 'password' in kwargs.keys(): self.password = kwargs['password'] else: raise JiraException( 'You need to specify a password as keyword argument!') try: self.client = JIRA(self.options, basic_auth=(self.username, self.password)) except Exception: raise JiraException( 'Could not connect to the API, invalid username or password!' ) def get_projects(self, raw=False): projects = [] for project in self.client.projects(): if raw: projects.append(project) else: projects.append({ 'Name': str(project.key), 'Description': str(project.name) }) return projects @staticmethod def date_create(): my_date = datetime.datetime.now().date() return my_date @staticmethod def sixth_day(): now = datetime.datetime.now().date() start_month = datetime.datetime(now.year, now.month, 1) date_on_next_month = start_month + datetime.timedelta(35) start_next_month = datetime.datetime(date_on_next_month.year, date_on_next_month.month, 1) last_day_month = start_next_month - datetime.timedelta(1) sixth_day_month = last_day_month.date() + timedelta(6) return sixth_day_month def issue_description(self): description = "Hi Team" + "\n" + "Hope you are doing well" + "\n" + \ "Kindly confirm the current advertisers and accounts from the PDF attached." + "\n" + \ "Making sure that for each platform the correct account id, advertiser or account name get" + \ "\n" + "included in the NDP data for ALL channels." + "\n" + \ "Note: Do let us know if there is any account that needs to be removed." + "\n" + \ "Please make sure that the media plan is uploaded on the NeoSageCentral SharePoint Folder." \ + "\n" \ + "https://insidemedia.sharepoint.com/sites/neosagecentral/Shared%20Documents/" \ "Forms/AllItems.aspx?id=%2Fsites%2Fneosagecentral%2FShared%20Documents%2FFY18%20Media%" \ "20Plans%2FQ4%20Media%20Plans" + "\n" + "Thanks," + "\n" + "Nikita" self.description = description def de(self): watch = [ 'Dharmendra.mishra', 'ankit.singhal', 'ubaidullah.arifshah', 'ayush.sharma', 'nikita.borah', 'deepak.garg', 'mohammad.dilshad', 'jennifer.marquez', 'ajaysingh.yadav', 'Ing-y.Chenn', 'anastasia.lanina', 'thilo.babel1' ] assignees = 'anastasia.lanina' attachment = [self.section_value[3] + 'DE.xlsx'] try: self.issue_de = { 'project': { 'key': 'MOS' }, 'issuetype': { 'name': 'Reporting' }, 'summary': 'NDP Data Audit {} DE'.format( Jira.date_create().strftime('%B')), 'description': self.description, 'customfield_10038': { 'value': 'DE' }, 'customfield_10052': { 'value': 'Ad hoc' }, 'customfield_10053': { 'value': 'Monthly' }, "assignee": { "name": assignees }, 'duedate': str(Jira.sixth_day()) } create_de = self.client.create_issue(fields=self.issue_de, prefetch=True) self.create_de = create_de for j in attachment: self.client.add_attachment(self.create_de.id, j) for i in watch: self.client.add_watcher(self.create_de.id, i) except JIRAError as e: logger.error(str(e)) pass def fr(self): watch = [ 'Dharmendra.mishra', 'ankit.singhal', 'ubaidullah.arifshah', 'ayush.sharma', 'nikita.borah', 'deepak.garg', 'mohammad.dilshad', 'jennifer.marquez', 'ajaysingh.yadav', 'Maxime.Sarrazin', 'philippine.gurs', 'manon.mercier' ] assignees = 'manon.mercier' attachment = [self.section_value[3] + 'FR.xlsx'] try: self.issue_fr = { 'project': { 'key': 'MOS' }, 'issuetype': { 'name': 'Reporting' }, 'summary': 'NDP Data Audit {} FR'.format( Jira.date_create().strftime('%B')), 'description': self.description, 'customfield_10038': { 'value': 'FR' }, 'customfield_10052': { 'value': 'Ad hoc' }, 'customfield_10053': { 'value': 'Monthly' }, "assignee": { "name": assignees }, 'duedate': str(Jira.sixth_day()) } self.create_fr = self.client.create_issue(fields=self.issue_fr, prefetch=True) for j in attachment: self.client.add_attachment(self.create_fr.id, j) for i in watch: self.client.add_watcher(self.create_fr.id, i) except JIRAError as e: logger.error(str(e)) pass def es(self): watch = [ 'Dharmendra.mishra', 'ankit.singhal', 'ubaidullah.arifshah', 'ayush.sharma', 'nikita.borah', 'deepak.garg', 'mohammad.dilshad', 'jennifer.marquez', 'ajaysingh.yadav', 'silvia.orofino', 'carmen.candela', 'raquel.hernandez' ] assignees = 'silvia.orofino' attachment = [ self.section_value[3] + 'ES.xlsx', self.section_value[3] + 'PT.xlsx' ] try: self.issue_es = { 'project': { 'key': 'MOS' }, 'issuetype': { 'name': 'Reporting' }, 'summary': 'NDP Data Audit {} ES'.format( Jira.date_create().strftime('%B')), 'description': self.description, 'customfield_10038': { 'value': 'ES' }, 'customfield_10052': { 'value': 'Ad hoc' }, 'customfield_10053': { 'value': 'Monthly' }, "assignee": { "name": assignees }, 'duedate': str(Jira.sixth_day()) } self.create_es = self.client.create_issue(fields=self.issue_es, prefetch=True) for j in attachment: self.client.add_attachment(self.create_es.id, j) for i in watch: self.client.add_watcher(self.create_es.id, i) except JIRAError as e: logger.error(str(e)) pass def us(self): watch = [ 'Dharmendra.mishra', 'ankit.singhal', 'ubaidullah.arifshah', 'ayush.sharma', 'nikita.borah', 'deepak.garg', 'mohammad.dilshad', 'jennifer.marquez', 'ajaysingh.yadav', 'vanessa.ezeta' ] assignees = 'vanessa.ezeta' attachment = [ self.section_value[3] + 'CA.xlsx', self.section_value[3] + 'US.xlsx', self.section_value[3] + 'US & CA.xlsx' ] try: self.issue_us = { 'project': { 'key': 'MOS' }, 'issuetype': { 'name': 'Reporting' }, 'summary': 'NDP Data Audit {} US'.format( Jira.date_create().strftime('%B')), 'description': self.description, 'customfield_10038': { 'value': 'US' }, 'customfield_10052': { 'value': 'Ad hoc' }, 'customfield_10053': { 'value': 'Monthly' }, "assignee": { "name": assignees }, 'duedate': str(Jira.sixth_day()) } self.create_us = self.client.create_issue(fields=self.issue_us, prefetch=True) for j in attachment: self.client.add_attachment(self.create_us.id, j) for i in watch: self.client.add_watcher(self.create_us.id, i) except JIRAError as e: logger.error(str(e)) pass def uk(self): watch = [ 'Dharmendra.mishra', 'ankit.singhal', 'ubaidullah.arifshah', 'ayush.sharma', 'nikita.borah', 'deepak.garg', 'mohammad.dilshad', 'jennifer.marquez', 'ajaysingh.yadav', 'aman.mastana', 'manon.leymat', 'james.hill', 'alex.ooi', 'sophie.corcinos', 'mireia.nadal', 'nick.gardiner' ] assignees = 'manon.leymat' attachment = [ self.section_value[3] + 'UK.xlsx', self.section_value[3] + 'IE.xlsx' ] try: self.issue_uk = { 'project': { 'key': 'MOS' }, 'issuetype': { 'name': 'Reporting' }, 'summary': 'NDP Data Audit {} UK'.format( Jira.date_create().strftime('%B')), 'description': self.description, 'customfield_10038': { 'value': 'UK' }, 'customfield_10052': { 'value': 'Ad hoc' }, 'customfield_10053': { 'value': 'Monthly' }, "assignee": { "name": assignees }, 'duedate': str(Jira.sixth_day()) } self.create_uk = self.client.create_issue(fields=self.issue_uk, prefetch=True) for j in attachment: self.client.add_attachment(self.create_uk.id, j) for i in watch: self.client.add_watcher(self.create_uk.id, i) except JIRAError as e: logger.error(str(e)) pass # Done def mea(self): watch = [ 'Dharmendra.mishra', 'ankit.singhal', 'ubaidullah.arifshah', 'ayush.sharma', 'nikita.borah', 'deepak.garg', 'mohammad.dilshad', 'jennifer.marquez', 'ajaysingh.yadav', 'marthinus.matthee', 'Brunette Nkuna' ] assignees = 'gareth.macgregor' attachment = [self.section_value[3] + 'ZA.xlsx'] try: self.issue_mea = { 'project': { 'key': 'MOS' }, 'issuetype': { 'name': 'Reporting' }, 'summary': 'NDP Data Audit {} MEA'.format( Jira.date_create().strftime('%B')), 'description': self.description, 'customfield_10038': { 'value': 'MEA' }, 'customfield_10052': { 'value': 'Ad hoc' }, 'customfield_10053': { 'value': 'Monthly' }, "assignee": { "name": assignees }, 'duedate': str(Jira.sixth_day()) } self.create_mea = self.client.create_issue(fields=self.issue_mea, prefetch=True) for j in attachment: self.client.add_attachment(self.create_mea.id, j) for i in watch: self.client.add_watcher(self.create_mea.id, i) except JIRAError as e: logger.error(str(e)) pass def aus(self): watch = [ 'Dharmendra.mishra', 'ankit.singhal', 'ubaidullah.arifshah', 'ayush.sharma', 'nikita.borah', 'deepak.garg', 'mohammad.dilshad', 'jennifer.marquez', 'ajaysingh.yadav', 'jon.windred', 'corinne.hewlett' ] assignees = 'corinne.hewlett' attachment = [self.section_value[3] + 'AUS.xlsx'] try: self.issue_aus = { 'project': { 'key': 'MOS' }, 'issuetype': { 'name': 'Reporting' }, 'summary': 'NDP Data Audit {} AUS'.format( Jira.date_create().strftime('%B')), 'description': self.description, 'customfield_10038': { 'value': 'AUS' }, 'customfield_10052': { 'value': 'Ad hoc' }, 'customfield_10053': { 'value': 'Monthly' }, "assignee": { "name": assignees }, 'duedate': str(Jira.sixth_day()) } self.create_aus = self.client.create_issue(fields=self.issue_aus, prefetch=True) for j in attachment: self.client.add_attachment(self.create_aus.id, j) for i in watch: self.client.add_watcher(self.create_aus.id, i) except JIRAError as e: logger.error(str(e)) pass def apac(self): watch = [ 'Dharmendra.mishra', 'ankit.singhal', 'ubaidullah.arifshah', 'ayush.sharma', 'nikita.borah', 'deepak.garg', 'mohammad.dilshad', 'jennifer.marquez', 'ajaysingh.yadav', 'ravi.chettiar' ] assignees = 'ravi.chettiar' attachment = [ self.section_value[3] + 'HK.xlsx', self.section_value[3] + 'ID.xlsx', self.section_value[3] + 'MY.xlsx', self.section_value[3] + 'SG.xlsx', self.section_value[3] + 'TH.xlsx' ] try: self.issue_apac = { 'project': { 'key': 'MOS' }, 'issuetype': { 'name': 'Reporting' }, 'summary': 'NDP Data Audit {} APAC'.format( Jira.date_create().strftime('%B')), 'description': self.description, 'customfield_10038': { 'value': 'APAC' }, 'customfield_10052': { 'value': 'Ad hoc' }, 'customfield_10053': { 'value': 'Monthly' }, "assignee": { "name": assignees }, 'duedate': str(Jira.sixth_day()) } self.create_apac = self.client.create_issue(fields=self.issue_apac, prefetch=True) for j in attachment: self.client.add_attachment(self.create_apac.id, j) for i in watch: self.client.add_watcher(self.create_apac.id, i) except JIRAError as e: logger.error(str(e)) pass def main(self): self.get_projects() logger.info('reading issue Description' + " at " + str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))) self.issue_description() logger.info('Done!reading issue Description' + " at " + str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))) logger.info('Start!creating issue for DE' + " at " + str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))) self.de() logger.info('Done!creating issue for DE' + " at " + str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))) logger.info('Start!creating issue for FR' + " at " + str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))) self.fr() logger.info('Done!creating issue for FR' + " at " + str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))) logger.info('Start!creating issue for ES' + " at " + str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))) self.es() logger.info('Done!creating issue for ES' + " at " + str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))) logger.info('Start!creating issue for US' + " at " + str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))) self.us() logger.info('Done!creating issue for US' + " at " + str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))) logger.info('Start!creating issue for UK' + " at " + str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))) self.uk() logger.info('Done!creating issue for UK' + " at " + str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))) logger.info('Start!creating issue for MEA' + " at " + str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))) self.mea() logger.info('Done!creating issue for MEA' + " at " + str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))) logger.info('Start!creating issue for AUS' + " at " + str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))) self.aus() logger.info('Done!creating issue for AUS' + " at " + str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))) logger.info('Start!creating issue for APAC' + " at " + str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))) self.apac() logger.info('Done!creating issue for APAC' + " at " + str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M")))
class Jira(object): def __init__(self, **kwargs): self.description = None self.issue_aus = None self.issue_us = None self.issue_uk = None self.issue_es = None self.issue_de = None self.issue_fr = None self.issue_apac = None self.issue_mea = None self.create_aus = None self.create_us = None self.create_apac = None self.create_de = None self.create_mea = None self.create_fr = None self.create_es = None self.create_uk = None self.options = { 'server': 'https://neomediaworld.atlassian.net', 'verify': False } self.client = None if len(kwargs) != 2: raise JiraException( 'In order to use this class you need to specify a user and a password as keyword arguments!' ) else: if 'username' in kwargs.keys(): self.username = kwargs['username'] else: raise JiraException( 'You need to specify a username as keyword argument!') if 'password' in kwargs.keys(): self.password = kwargs['password'] else: raise JiraException( 'You need to specify a password as keyword argument!') try: self.client = JIRA(self.options, basic_auth=(self.username, self.password)) except Exception: raise JiraException( 'Could not connect to the API, invalid username or password!' ) def get_projects(self, raw=False): projects = [] for project in self.client.projects(): if raw: projects.append(project) else: projects.append({ 'Name': str(project.key), 'Description': str(project.name) }) return projects @staticmethod def date_create(): my_date = datetime.datetime.now().date() return my_date @staticmethod def sixth_day(): now = datetime.datetime.now().date() start_month = datetime.datetime(now.year, now.month, 1) date_on_next_month = start_month + datetime.timedelta(35) start_next_month = datetime.datetime(date_on_next_month.year, date_on_next_month.month, 1) last_day_month = start_next_month - datetime.timedelta(1) sixth_day_month = last_day_month.date() + timedelta(6) return sixth_day_month def issue_description(self): description = "Hi Team" + "\n" + "Hope you are doing well" + "\n" + \ "Kindly confirm the current advertisers and accounts from the PDF attached." + "\n" + \ "Making sure that for each platform the correct account id, advertiser or account name get" + \ "\n" + "included in the NDP data for ALL channels." + "\n" + \ "Note: Do let us know if there is any account that needs to be removed." + "\n" + \ "Please make sure that the media plan is uploaded on the NeoSageCentral SharePoint Folder." \ + "\n" \ + "https://insidemedia.sharepoint.com/sites/neosagecentral/Shared%20Documents/" \ "Forms/AllItems.aspx?id=%2Fsites%2Fneosagecentral%2FShared%20Documents%2FFY18%20Media%" \ "20Plans%2FQ4%20Media%20Plans" + "\n" + "Thanks," + "\n" + "Ubaid" self.description = description def de(self): watch = [ 'anastasia.lanina', 'Ing-y.Chenn', 'thilo.babel', 'jennifer.marquez', 'manon.leymat', 'mohammad.dilshad', 'deepak.garg', 'matthew.perrone' ] assignees = 'anastasia.lanina' attachment = ['C://Adops-Git//Files//DE.xlsx'] self.issue_de = { 'project': { 'key': 'MOS' }, 'issuetype': { 'name': 'Reporting' }, 'summary': 'NDP Data Audit {} DE'.format(Jira.date_create().strftime('%B')), 'description': self.description, 'customfield_10038': { 'value': 'DE' }, 'customfield_10052': { 'value': 'Ad hoc' }, 'customfield_10053': { 'value': 'Monthly' }, "assignee": { "name": assignees }, 'duedate': str(Jira.sixth_day()) } create_de = self.client.create_issue(fields=self.issue_de, prefetch=True) self.create_de = create_de for i in watch: self.client.add_watcher(self.create_de.id, i) for j in attachment: self.client.add_attachment(self.create_de.id, j) def fr(self): watch = [ 'manon.mercier', 'maxime.sarrazin', 'jennifer.marquez', 'manon.leymat', 'mohammad.dilshad', 'deepak.garg', 'matthew.perrone' ] assignees = 'manon.mercier' attachment = ['C://Adops-Git//Files//FR.xlsx'] self.issue_fr = { 'project': { 'key': 'MOS' }, 'issuetype': { 'name': 'Reporting' }, 'summary': 'NDP Data Audit {} FR'.format(Jira.date_create().strftime('%B')), 'description': self.description, 'customfield_10038': { 'value': 'FR' }, 'customfield_10052': { 'value': 'Ad hoc' }, 'customfield_10053': { 'value': 'Monthly' }, "assignee": { "name": assignees }, 'duedate': str(Jira.sixth_day()) } self.create_fr = self.client.create_issue(fields=self.issue_fr, prefetch=True) for i in watch: self.client.add_watcher(self.create_fr.id, i) for j in attachment: self.client.add_attachment(self.create_fr.id, j) def es(self): watch = [ 'carmen.candela', 'antonio.desantos', 'raquel.hernandez', 'jennifer.marquez', 'manon.leymat', 'mohammad.dilshad', 'deepak.garg', 'matthew.perrone' ] assignees = 'antonio.desantos' attachment = [ 'C://Adops-Git//Files//ES.xlsx', 'C://Adops-Git//Files//PT.xlsx' ] self.issue_es = { 'project': { 'key': 'MOS' }, 'issuetype': { 'name': 'Reporting' }, 'summary': 'NDP Data Audit {} ES'.format(Jira.date_create().strftime('%B')), 'description': self.description, 'customfield_10038': { 'value': 'ES' }, 'customfield_10052': { 'value': 'Ad hoc' }, 'customfield_10053': { 'value': 'Monthly' }, "assignee": { "name": assignees }, 'duedate': str(Jira.sixth_day()) } self.create_es = self.client.create_issue(fields=self.issue_es, prefetch=True) for i in watch: self.client.add_watcher(self.create_es.id, i) for j in attachment: self.client.add_attachment(self.create_es.id, j) def us(self): watch = [ 'christine.ciarcia', 'vanessa.ezeta', 'jennifer.marquez', 'manon.leymat', 'mohammad.dilshad', 'deepak.garg', 'matthew.perrone' ] assignees = 'vanessa.ezeta' attachment = [ 'C://Adops-Git//Files//CA.xlsx', 'C://Adops-Git//Files//US.xlsx', 'C://Adops-Git//Files//US & CA.xlsx' ] self.issue_us = { 'project': { 'key': 'MOS' }, 'issuetype': { 'name': 'Reporting' }, 'summary': 'NDP Data Audit {} US'.format(Jira.date_create().strftime('%B')), 'description': self.description, 'customfield_10038': { 'value': 'US' }, 'customfield_10052': { 'value': 'Ad hoc' }, 'customfield_10053': { 'value': 'Monthly' }, "assignee": { "name": assignees }, 'duedate': str(Jira.sixth_day()) } self.create_us = self.client.create_issue(fields=self.issue_us, prefetch=True) for i in watch: self.client.add_watcher(self.create_us.id, i) for j in attachment: self.client.add_attachment(self.create_us.id, j) def uk(self): watch = [ 'mireia.nadal', 'marta.fiascaris', 'nick.gardiner', 'yasmin.andrews', 'ye-eun.kim', 'jennifer.marquez', 'manon.leymat', 'mohammad.dilshad', 'deepak.garg', 'matthew.perrone' ] assignees = 'ye-eun.kim' attachment = [ 'C://Adops-Git//Files//UK.xlsx', 'C://Adops-Git//Files//IE.xlsx' ] self.issue_uk = { 'project': { 'key': 'MOS' }, 'issuetype': { 'name': 'Reporting' }, 'summary': 'NDP Data Audit {} UK'.format(Jira.date_create().strftime('%B')), 'description': self.description, 'customfield_10038': { 'value': 'UK' }, 'customfield_10052': { 'value': 'Ad hoc' }, 'customfield_10053': { 'value': 'Monthly' }, "assignee": { "name": assignees }, 'duedate': str(Jira.sixth_day()) } self.create_uk = self.client.create_issue(fields=self.issue_uk, prefetch=True) for i in watch: self.client.add_watcher(self.create_uk.id, i) for j in attachment: self.client.add_attachment(self.create_uk.id, j) def mea(self): watch = [ 'cindy.booysen', 'kyle.ackermann', 'jennifer.marquez', 'manon.leymat', 'mohammad.dilshad', 'deepak.garg', 'matthew.perrone' ] assignees = 'cindy.booysen' attachment = ['C://Adops-Git//Files//ZA.xlsx'] self.issue_mea = { 'project': { 'key': 'MOS' }, 'issuetype': { 'name': 'Reporting' }, 'summary': 'NDP Data Audit {} MEA'.format(Jira.date_create().strftime('%B')), 'description': self.description, 'customfield_10038': { 'value': 'MEA' }, 'customfield_10052': { 'value': 'Ad hoc' }, 'customfield_10053': { 'value': 'Monthly' }, "assignee": { "name": assignees }, 'duedate': str(Jira.sixth_day()) } self.create_mea = self.client.create_issue(fields=self.issue_mea, prefetch=True) for i in watch: self.client.add_watcher(self.create_mea.id, i) for j in attachment: self.client.add_attachment(self.create_mea.id, j) def aus(self): watch = [ 'corinne.hewlett', 'thuy.le', 'jon.windred', 'jennifer.marquez', 'manon.leymat', 'mohammad.dilshad', 'deepak.garg', 'matthew.perrone' ] assignees = 'corinne.hewlett' attachment = ["C://Adops-Git//Files//AUS.xlsx"] self.issue_aus = { 'project': { 'key': 'MOS' }, 'issuetype': { 'name': 'Reporting' }, 'summary': 'NDP Data Audit {} AUS'.format(Jira.date_create().strftime('%B')), 'description': self.description, 'customfield_10038': { 'value': 'AUS' }, 'customfield_10052': { 'value': 'Ad hoc' }, 'customfield_10053': { 'value': 'Monthly' }, "assignee": { "name": assignees }, 'duedate': str(Jira.sixth_day()) } self.create_aus = self.client.create_issue(fields=self.issue_aus, prefetch=True) for i in watch: self.client.add_watcher(self.create_aus.id, i) for j in attachment: self.client.add_attachment(self.create_aus.id, j) def apac(self): watch = [ 'ravi.chettiar', 'jennifer.marquez', 'manon.leymat', 'mohammad.dilshad', 'deepak.garg', 'matthew.perrone' ] assignees = 'ravi.chettiar' attachment = [ 'C://Adops-Git//Files//HK.xlsx', 'C://Adops-Git//Files//ID.xlsx', 'C://Adops-Git//Files//MY.xlsx', 'C://Adops-Git//Files//SG.xlsx', 'C://Adops-Git//Files//TH.xlsx' ] self.issue_apac = { 'project': { 'key': 'MOS' }, 'issuetype': { 'name': 'Reporting' }, 'summary': 'NDP Data Audit {} APAC'.format(Jira.date_create().strftime('%B')), 'description': self.description, 'customfield_10038': { 'value': 'APAC' }, 'customfield_10052': { 'value': 'Ad hoc' }, 'customfield_10053': { 'value': 'Monthly' }, "assignee": { "name": assignees }, 'duedate': str(Jira.sixth_day()) } self.create_apac = self.client.create_issue(fields=self.issue_apac, prefetch=True) for i in watch: self.client.add_watcher(self.create_apac.id, i) for j in attachment: self.client.add_attachment(self.create_apac.id, j) def main(self): self.get_projects() logger.info('reading issue Description' + " at " + str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))) self.issue_description() logger.info('Done!reading issue Description' + " at " + str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))) logger.info('Start!creating issue for DE' + " at " + str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))) self.de() logger.info('Done!creating issue for DE' + " at " + str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))) logger.info('Start!creating issue for FR' + " at " + str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))) self.fr() logger.info('Done!creating issue for FR' + " at " + str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))) logger.info('Start!creating issue for ES' + " at " + str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))) self.es() logger.info('Done!creating issue for ES' + " at " + str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))) logger.info('Start!creating issue for US' + " at " + str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))) self.us() logger.info('Done!creating issue for US' + " at " + str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))) logger.info('Start!creating issue for UK' + " at " + str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))) self.uk() logger.info('Done!creating issue for UK' + " at " + str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))) logger.info('Start!creating issue for MEA' + " at " + str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))) self.mea() logger.info('Done!creating issue for MEA' + " at " + str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))) logger.info('Start!creating issue for AUS' + " at " + str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))) self.aus() logger.info('Done!creating issue for AUS' + " at " + str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))) logger.info('Start!creating issue for APAC' + " at " + str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))) self.apac() logger.info('Done!creating issue for APAC' + " at " + str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M")))