Пример #1
0
def enumerate_pending(jira):
    since = datetime.datetime.now() - datetime.timedelta(days=7)

    jql = default_jql()
    jql += "AND status = 'In Progress' \
            AND issuetype != Initiative \
            AND issuetype != Epic"

    vprint(jql)

    my_issues = jira.search_issues(jql,
                                   expand="changelog",
                                   fields="summary,assignee,created")
    if my_issues.total > my_issues.maxResults:
        my_issues = jira.search_issues(jql,
                                       expand="changelog",
                                       fields="summary,assignee,created",
                                       maxResults=my_issues.total)

    for issue in my_issues:
        status = {}
        status['issue'] = str(issue)
        if issue.fields.assignee:
            status['assignee'] = issue.fields.assignee.displayName
        else:
            status['assignee'] = 'Unassigned'
        status['summary'] = issue.fields.summary

        created = datetime.datetime.strptime(issue.fields.created,
                                             '%Y-%m-%dT%H:%M:%S.%f%z')
        status['new'] = created.replace(tzinfo=None) > since

        yield (status)
Пример #2
0
def enumerate_pending(jira):
    user = cfg.args.user

    since = datetime.datetime.now() - datetime.timedelta(days=7)

    jql = "(project = QLT OR assignee in membersOf('linaro-landing-team-qualcomm')) \
           AND status = 'In Progress' \
           AND issuetype != Initiative \
           AND issuetype != Epic"
    if user:
       jql += ' AND assignee = "%s"' % add_domain(user)
    vprint(jql)

    my_issues = jira.search_issues(jql, expand="changelog", fields="summary,assignee,created")
    if my_issues.total > my_issues.maxResults:
        my_issues = jira.search_issues(jql, expand="changelog", fields="summary,assignee,created",
                                       maxResults=my_issues.total)

    for issue in my_issues:
        status = {}
        status['issue'] = str(issue)
        if issue.fields.assignee:
            status['assignee'] = issue.fields.assignee.displayName
        else:
            status['assignee'] = 'Unassigned'
        status['summary'] = issue.fields.summary

        created = datetime.datetime.strptime(issue.fields.created, '%Y-%m-%dT%H:%M:%S.%f%z')
        status['new'] = created.replace(tzinfo=None) > since

        yield(status)
Пример #3
0
def enumerate_updates(jira):
    since = datetime.datetime.now() - datetime.timedelta(
        days=int(cfg.args.days))

    jql = default_jql()
    jql += "AND updatedDate > -%sd" % cfg.args.days
    vprint(jql)

    my_issues = jira.search_issues(jql,
                                   expand="changelog",
                                   fields="summary,comment,assignee,created")
    if my_issues.total > my_issues.maxResults:
        my_issues = jira.search_issues(
            jql,
            expand="changelog",
            fields="summary,comment,assignee,created",
            maxResults=my_issues.total)

    for issue in my_issues:
        changelog = issue.changelog
        comments = issue.fields.comment.comments

        status = {}
        status['issue'] = str(issue)
        if issue.fields.assignee:
            status['assignee'] = issue.fields.assignee.displayName
        else:
            status['assignee'] = 'Unassigned'
        status['summary'] = issue.fields.summary
        status['comments'] = []
        status['resolution'] = None

        created = datetime.datetime.strptime(issue.fields.created,
                                             '%Y-%m-%dT%H:%M:%S.%f%z')
        if created.replace(tzinfo=None) > since:
            status['resolution'] = 'Created'

        for comment in comments:
            when = datetime.datetime.strptime(comment.created,
                                              '%Y-%m-%dT%H:%M:%S.%f%z')
            if when.replace(tzinfo=None) < since:
                continue

            status['comments'].append(comment.body)

        for history in changelog.histories:
            when = datetime.datetime.strptime(history.created,
                                              '%Y-%m-%dT%H:%M:%S.%f%z')
            if when.replace(tzinfo=None) < since:
                continue
            for item in history.items:
                if item.field == 'resolution':
                    status['resolution'] = item.toString

        if len(status['comments']) != 0 or status['resolution']:
            yield (status)
Пример #4
0
def open_file(filename):
    """
    This will open the user provided file and if there has not been any file
    provided it will create and open a temporary file instead.
    """
    vprint("filename: %s\n" % filename)
    if filename:
        return open(filename, "w")
    else:
        return tempfile.NamedTemporaryFile(mode='w+t', delete=False)
Пример #5
0
def initiate_config():
    """ Reads the config file (yaml format) and returns the sets the global
    instance.
    """
    cfg.config_file = get_config_file()
    if not os.path.isfile(cfg.config_file):
        create_default_config()

    vprint("Using config file: %s" % cfg.config_file)
    with open(cfg.config_file, 'r') as yml:
        cfg.yml_config = yaml.load(yml)
Пример #6
0
def build_epics_node(jira, epic_key, d_handled=None, initiative_node=None):
    ei = jira.issue(epic_key)

    if ei.fields.status.name in ["Closed", "Resolved"]:
        d_handled[str(ei.key)] = [None, ei]
        return None

    summary = str(ei.fields.summary.encode('ascii', 'ignore').decode())
    epic = Node(str(ei.key), summary, str(ei.fields.issuetype))

    try:
        assignee = str(
            ei.fields.assignee.displayName.encode('ascii', 'ignore').decode())
    except AttributeError:
        assignee = str(ei.fields.assignee)
    epic.add_assignee(assignee)

    epic.set_state(str(ei.fields.status.name))

    try:
        sponsors = ei.fields.customfield_10101
        if sponsors is not None:
            for s in sponsors:
                epic.add_sponsor(str(s.value))
    except AttributeError:
        epic.add_sponsor("No sponsor")

    epic.set_base_url(cfg.server)

    if initiative_node is not None:
        epic.add_parent(initiative_node.get_key())
        initiative_node.add_child(epic)
    else:
        # This cateches when people are not using implements/implemented by, but
        # there is atleast an "Initiative" link that we can use.
        parent = get_parent_key(jira, ei)
        if parent is not None and parent in d_handled:
            parent_node = d_handled[parent][0]
            if parent_node is not None:
                epic.add_parent(parent_node)
                parent_node.add_child(epic)
            else:
                vprint("Didn't find any parent")

    d_handled[epic.get_key()] = [epic, ei]

    # Deal with stories
    for link in ei.fields.issuelinks:
        if "inwardIssue" in link.raw:
            story_key = str(link.inwardIssue.key)
            build_story_node(jira, story_key, d_handled, epic)

    print(epic)
    return epic
Пример #7
0
def write_last_jira_comment(f, jira, issue):
    """ Pulls the last comment from Jira from an issue and writes it to the file
    object.
    """
    c = jira.comments(issue)
    if len(c) > 0:
        try:
            comment = "# Last comment:\n# ---8<---\n# %s\n# --->8---\n" % \
                        "\n# ".join(c[-1].body.splitlines())
            f.write(comment)
        except UnicodeEncodeError:
            vprint("Can't encode character")
Пример #8
0
def update_jira(jira, i, c):
    """
    This is the function that do the actual updates to Jira and in this case it
    is adding comments to a certain issue.
    """
    vprint("Updating Jira issue: %s with comment:" % i)
    vprint(
        "-- 8< --------------------------------------------------------------------------"
    )
    vprint("%s" % c)
    vprint(
        "-- >8 --------------------------------------------------------------------------\n\n"
    )
    if not cfg.args.dry_run:
        jira.add_comment(i, c)
Пример #9
0
def build_story_node(jira, story_key, d_handled=None, epic_node=None):
    si = jira.issue(story_key)
    if si.fields.status.name in ["Closed", "Resolved"]:
        d_handled[str(si.key)] = [None, si]
        return None

    # To prevent UnicodeEncodeError ignore unicode
    summary = str(si.fields.summary.encode('ascii', 'ignore').decode())
    story = Node(str(si.key), summary, str(si.fields.issuetype))

    try:
        assignee = str(
            si.fields.assignee.displayName.encode('ascii', 'ignore').decode())
    except AttributeError:
        assignee = str(si.fields.assignee)
    story.add_assignee(assignee)

    story.set_state(str(si.fields.status.name))
    story.set_base_url(cfg.server)

    if epic_node is not None:
        story.add_parent(epic_node.get_key())
        epic_node.add_child(story)
    else:
        # This cateches when people are not using implements/implemented by, but
        # there is atleast an "Epic" link that we can use.
        parent = get_parent_key(jira, si)
        if parent is not None and parent in d_handled:
            parent_node = d_handled[parent][0]
            if parent_node is not None:
                story.add_parent(parent_node)
                parent_node.add_child(story)
            else:
                vprint("Didn't find any parent")

    print(story)
    d_handled[story.get_key()] = [story, si]
    return story
Пример #10
0
def build_orphans_tree(jira, key, d_handled):
    jql = "project=%s" % (key)
    all_issues = jira.search_issues(jql)

    orphans_initiatives = []
    orphans_epics = []
    orphans_stories = []
    for i in all_issues:
        if str(i.key) not in d_handled:
            if i.fields.status.name in ["Closed", "Resolved"]:
                continue
            else:
                if i.fields.issuetype.name == "Initiative":
                    orphans_initiatives.append(i)
                elif i.fields.issuetype.name == "Epic":
                    orphans_epics.append(i)
                elif i.fields.issuetype.name == "Story":
                    orphans_stories.append(i)

    # Now we three list of Jira tickets not touched before, let's go over them
    # staring with Initiatives, then Epics and last Stories. By doing so we
    # should get them nicely layed out in the orphan part of the tree.

    nodes = []
    vprint("Orphan Initiatives ...")
    for i in orphans_initiatives:
        node = build_initiatives_node(jira, i, d_handled)
        nodes.append(node)

    vprint("Orphan Epics ...")
    for i in orphans_epics:
        node = build_epics_node(jira, str(i.key), d_handled)
        nodes.append(node)

    vprint("Orphan Stories ...")
    for i in orphans_stories:
        node = build_story_node(jira, str(i.key), d_handled)
        nodes.append(node)

    return nodes
Пример #11
0
def parse_status_file(jira, filename):
    """
    The main parsing function, which will decide what should go into the actual
    Jira call. This for example removes the beginning until it finds a
    standalone [ISSUE] tag. It will also remove all comments prefixed with '#'.
    """
    # Regexp to match Jira issue on a single line, i.e:
    # [SWG-28]
    # [LITE-32]
    # ...
    regex = r"^\[([A-Z]+-[0-9]+).*\]\n$"

    # Regexp to match a tag that indicates we should stop processing, ex:
    # [STOP]
    # [JIPDATE-STOP]
    # [OTHER]
    # ...
    regex_stop = r"^\[.*\]\n$"

    # Regexp to mach a tag that indicates to stop processing completely:
    # [FIN]
    regex_fin = r"^\[FIN\]\n$"

    # Contains the status text, it could be a file or a status email
    status = ""

    with open(filename) as f:
        status = f.readlines()

    myissue = ""
    mycomment = ""

    # build list of {issue-key,comment} tuples found in status
    issue_comments = []
    for line in status:
        # New issue?
        match = re.search(regex, line)
        if match:
            myissue = match.group(1)
            validissue = True

            try:
                issue = jira.issue(myissue)
            except Exception as e:
                if 'Issue Does Not Exist' in e.text:
                    print('[{}] :  {}'.format(myissue, e.text))
                    validissue = False

            if validissue:
                issue_comments.append((myissue, ""))
        # Stop parsing entirely.  This needs to be placed before regex_stop
        # or the .* will match and [FIN] won't be processed
        elif re.search(regex_fin, line):
            break
        # If we have non-JIRA issue tags, stop parsing until we find a valid tag
        elif re.search(regex_stop, line):
            validissue = False
        else:
            # Don't add lines with comments
            if (line[0] != "#" and issue_comments and validissue):
                (i, c) = issue_comments[-1]
                issue_comments[-1] = (i, c + line)

    issue_upload = []
    print("These JIRA cards will be updated as follows:\n")
    for (idx, t) in enumerate(issue_comments):
        (issue, comment) = issue_comments[idx]

        # Strip beginning  and trailing blank lines
        comment = comment.strip('\n')

        if comment == "":
            vprint("Issue [%s] has no comment, not updating the issue" %
                   (issue))
            continue

        issue_upload.append((issue, comment))
        print("[%s]\n  %s" % (issue, "\n  ".join(comment.splitlines())))
    print("")

    issue_comments = issue_upload
    if issue_comments == [] or cfg.args.dry_run or should_update() == "n":
        if issue_comments == []:
            print("No change, Jira was not updated!\n")
        else:
            print("Comments will not be written to Jira!\n")
        if not cfg.args.s:
            print_status(status)
        sys.exit()

    # if we found something, let's update jira
    for (issue, comment) in issue_comments:
        update_jira(jira, issue, comment)

    print("Successfully updated your Jira tickets!\n")
    if not cfg.args.s:
        print_status(status)
Пример #12
0
def get_jira_issues(jira, username):
    """
    Query Jira and then creates a status update file (either temporary or named)
    containing all information found from the JQL query.
    """
    exclude_stories = cfg.args.x
    epics_only = cfg.args.e
    all_status = cfg.args.all
    filename = cfg.args.file
    user = cfg.args.user
    last_comment = cfg.args.l

    issue_types = ["Epic"]
    if not epics_only:
        issue_types.append("Initiative")
        if not exclude_stories:
            issue_types.append("Story")
    issue_type = "issuetype in (%s)" % ", ".join(issue_types)

    status = "status in (\"In Progress\")"
    if all_status:
        status = "status not in (Resolved, Closed)"

    if user is None:
        user = "******"
    else:
        user = "******"%s\"" % add_domain(user)

    jql = "%s AND assignee = %s AND %s" % (issue_type, user, status)
    vprint(jql)

    my_issues = jira.search_issues(jql)

    showdate = strftime("%Y-%m-%d", gmtime())
    subject = "Subject: [Weekly] Week ending " + showdate + "\n\n"

    msg = get_header()
    if msg != "":
        msg += email_to_name(username) + "\n\n"

    f = open_file(filename)
    filename = f.name

    f.write(subject)

    f.write(msg)
    vprint("Found issue:")
    for issue in my_issues:
        vprint("%s : %s" % (issue, issue.fields.summary))

        if (merge_issue_header()):
            f.write("[%s%s%s]\n" %
                    (issue, get_header_separator(), issue.fields.summary))
        else:
            f.write("[%s]\n" % issue)
            f.write("# Header: %s\n" % issue.fields.summary)

        f.write("# Type: %s\n" % issue.fields.issuetype)
        f.write("# Status: %s\n" % issue.fields.status)
        f.write(get_extra_comments())
        if last_comment:
            write_last_jira_comment(f, jira, issue)
        f.write("\n")

    f.close()
    return filename
Пример #13
0
def parse_status_file(jira, filename, issues):
    """
    The main parsing function, which will decide what should go into the actual
    Jira call. This for example removes the beginning until it finds a
    standalone [ISSUE] tag. It will also remove all comments prefixed with '#'.
    """
    # Regexp to match Jira issue on a single line, i.e:
    # [SWG-28]
    # [LITE-32]
    # ...
    regex = r"^\[([A-Z]+-[0-9]+).*\]\n$"

    # Regexp to match a tag that indicates we should stop processing, ex:
    # [STOP]
    # [JIPDATE-STOP]
    # [OTHER]
    # ...
    regex_stop = r"^\[.*\]\n$"

    # Regexp to mach a tag that indicates to stop processing completely:
    # [FIN]
    regex_fin = r"^\[FIN\]\n$"

    # Regexp to match for a status update, this will remove 'Status' from the
    # match:
    regex_status = r'(?:^Status:) *(.+)\n$'

    # Contains the status text, it could be a file or a status email
    status = ""

    # List of resolutions (when doing a transition to Resolved). Query once globally.
    resolution_map = dict([(t.name.title(), t.id) for t in jira.resolutions()])

    with open(filename) as f:
        status = f.readlines()

    myissue = ""
    mycomment = ""

    # build list of {issue,comment} tuples found in status
    issue_comments = []
    for line in status:
        # New issue?
        match = re.search(regex, line)

        # Evaluate and save the transition regex for later. We have to do this
        # here, since we cannot assign and save the variable in the if
        # construction as you can do in C for example.
        transition = re.search(regex_status, line)

        if match:
            myissue = match.group(1)
            validissue = True

            # if we ran a query, we might already have fetched the issue
            # let's try to find the issue there first, otherwise ask Jira
            try:
                issue = [x for x in issues if str(x) == myissue][0]
                issue_comments.append((issue, "", ""))

            # IndexError: we had fetched already, but issue is not found
            # TypeError: issues is None, we haven't queried Jira yet, at all
            except (IndexError, TypeError) as e:
                try:
                    issue = jira.issue(myissue)
                    issue_comments.append((issue, "", ""))
                except Exception as e:
                    if 'Issue Does Not Exist' in e.text:
                        print('[{}] :  {}'.format(myissue, e.text))
                        validissue = False

        # Stop parsing entirely.  This needs to be placed before regex_stop
        # or the .* will match and [FIN] won't be processed
        elif re.search(regex_fin, line):
            break
        # If we have non-JIRA issue tags, stop parsing until we find a valid tag
        elif re.search(regex_stop, line):
            validissue = False
        elif transition and validissue:
            # If we have a match, then the new status should be first in the
            # group. Jira always expect the name of the state transitions to be
            # word capitalized, hence the call to the title() function. This
            # means that it doesn't matter if the user enter all lower case,
            # mixed or all upper case. All of them will work.
            new_status = transition.groups()[0].title()
            (i, c, _) = issue_comments[-1]
            issue_comments[-1] = (i, c, new_status)
        else:
            # Don't add lines with comments
            if (line[0] != "#" and issue_comments and validissue):
                (i, c, t) = issue_comments[-1]
                issue_comments[-1] = (i, c + line, t)

    issue_upload = []
    print("These JIRA cards will be updated as follows:\n")
    for (idx, t) in enumerate(issue_comments):
        (issue, comment, transition) = issue_comments[idx]

        # Strip beginning  and trailing blank lines
        comment = comment.strip('\n')

        # initialize here to avoid unassigned variables and useless code complexity
        resolution_id = transition_id = None
        resolution = transition_summary = ""

        if transition != "" and transition != str(issue.fields.status):
            # An optional 'resolution' attribute can be set when doing a transition
            # to Resolved, using the following pattern: Resolved / <resolution>
            if transition.startswith('Resolved') and '/' in transition:
                (transition, resolution) = map(str.strip,
                                               transition.split('/'))
                if not resolution in resolution_map:
                    print("Invalid resolution \"{}\" for issue {}".format(
                        resolution, issue))
                    print("Possible resolution: {}".format(
                        [t for t in resolution_map]))
                    sys.exit(1)
                resolution_id = resolution_map[resolution]

            transition_map = dict([(t['name'].title(), t['id'])
                                   for t in jira.transitions(issue)])
            if not transition in transition_map:
                print("Invalid transition \"{}\" for issue {}".format(
                    transition, issue))
                print("Possible transitions: {}".format(
                    [t for t in transition_map]))
                sys.exit(1)

            transition_id = transition_map[transition]
            if resolution:
                transition_summary = " %s => %s (%s)" % (
                    issue.fields.status, transition, resolution)
            else:
                transition_summary = " %s => %s" % (issue.fields.status,
                                                    transition)

        if comment == "" and not transition_id:
            vprint(
                "Issue [%s] has no comment or transitions, not updating the issue"
                % (issue))
            continue

        issue_upload.append((issue, comment, {
            'transition': transition_id,
            'resolution': resolution_id
        }))
        print("[%s]%s\n  %s" %
              (issue, transition_summary, "\n  ".join(comment.splitlines())))
    print("")

    issue_comments = issue_upload
    if issue_comments == [] or cfg.args.dry_run or should_update() == "n":
        if issue_comments == []:
            print("No change, Jira was not updated!\n")
        else:
            print("Comments will not be written to Jira!\n")
        if not cfg.args.s:
            print_status(status)
        sys.exit()

    # if we found something, let's update jira
    for (issue, comment, transition) in issue_comments:
        update_jira(jira, issue, comment, transition)

    print("Successfully updated your Jira tickets!\n")
    if not cfg.args.s:
        print_status(status)
Пример #14
0
def update_jira(jira, i, c, t):
    """
    This is the function that do the actual updates to Jira and in this case it
    is adding comments to a certain issue.
    """
    if t['transition']:
        if t['resolution']:
            vprint("Updating Jira issue: %s with transition: %s (%s)" %
                   (i, t['transition'], t['resolution']))
            jira.transition_issue(
                i,
                t['transition'],
                fields={'resolution': {
                    'id': t['resolution']
                }})
        else:
            vprint("Updating Jira issue: %s with transition: %s" %
                   (i, t['transition']))
            jira.transition_issue(i, t['transition'])

    if c != "":
        vprint("Updating Jira issue: %s with comment:" % i)
        vprint(
            "-- 8< --------------------------------------------------------------------------"
        )
        vprint("%s" % c)
        vprint(
            "-- >8 --------------------------------------------------------------------------\n\n"
        )
        jira.add_comment(i, c)