예제 #1
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'
        # 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
예제 #2
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])
예제 #3
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]) 
예제 #4
0
class jira_api():

    def __init__(self, server, user, pwd):
        """
        :param server: jira域名
        :param user:jira用户
        :param pwd:jira账户密码
        """
        self.server = server;
        self.basic_auth = (user, pwd)
        self.jiraClient = None

    def login(self):
        self.jiraClient = JIRA(server=self.server, basic_auth=self.basic_auth)
        if self.jiraClient is not None:
            return True
        else:
            return False


    def get_issue_list(self,jql_str=None,page=0,limit=10,fields=None):
        """
        查询issue集合
        :param jql_str: 查询语句
        :param page: 分页参数 开始
        :param limit: 分页参数 结束
        :param fields: 查询字段
        :return:issue: 列表
        """
        if self.jiraClient is None:
            self.login()
        if jql_str is None and jql_str == "":
            return []
        return self.jiraClient.search_issues(jql_str=jql_str, startAt=page, maxResults=limit, fields=fields)

    def get_issue_info(self,id_or_key,fields=None):
        """
        根据id或key查询issue信息
        :param id_or_key: issue id或key
        :param fields: 查询字段
        :return: issue详细信息
        """
        if self.jiraClient is None:
            self.login()
        if id_or_key is None:
            return {}
        return self.jiraClient.issue(id_or_key, fields=fields)

    def get_comments_from_issue(self,issue):
        """
        获取issue中所有comment集合
        :param issue:
        :return:
        """
        if self.jiraClient is None:
            self.login()
        if issue is None:
            return []
        return self.jiraClient.comments(issue)

    def get_comment_from_issue(self,issue,comment_id):
        if self.jiraClient is None:
            self.login()
        if issue is None:
            return {}
        if comment_id is None:
            return {}
        return self.jiraClient.comment(issue, comment_id)

    def add_comment_to_issue(self,issue_id,comment_str):
        if self.jiraClient is None:
            self.login()
        if issue_id is None or issue_id == "":
            return False
        if comment_str is None or comment_str == "":
            return False
        return self.jiraClient.add_comment(issue_id, comment_str)

    def update_comment(self,issue_id_or_key,comment_id,comment_str):
        if self.jiraClient is None:
            self.login()
        if issue_id_or_key is None or issue_id_or_key == "":
            return False
        if comment_id is None or comment_id == "":
            return False
        comment = self.get_comment_from_issue(self.get_issue_info(issue_id_or_key),comment_id)
        if comment is None:
            return False
        return comment.update(body=comment_str)

    def get_transitions_from_issue(self,issue):
        if self.jiraClient is None:
            self.login()
        if issue is None:
            return {}
        return self.jiraClient.transitions(issue)

    def assign_issue_to_user(self,issue_id,assignee):
        if self.jiraClient is None:
            self.login()
        if issue_id is None:
            return False
        if assignee is None:
            return False
        return self.jiraClient.assign_issue(issue_id, assignee)

    def transition_issues(self,issue_id,transition_id,assignee=None,comment=""):
        if self.jiraClient is None:
            self.login()
        if(issue_id is None):
            return False
        if(transition_id is None):
            return False
        if(comment is None):
            return False
        fields = None
        if assignee is not None and assignee != "":
            fields = {'assignee': {'name': assignee}}
        return self.jiraClient.transition_issue(issue=issue_id,fields=fields,transition=transition_id,comment=comment)

    def group_members(self,group_name):
        if self.jiraClient is None:
            self.login()
        if group_name is None:
            return []
        return self.jiraClient.group_members(group_name)

    def get_project_by_key(self,key):
        if self.jiraClient is None:
            self.login()
        if key is None:
            return ""
        return self.jiraClient.project(key)

    def get_custom_field_option(self,id):
        if self.jiraClient is None:
            self.login()
        if id is None:
            return ""
        return self.jiraClient.custom_field_option(id)