# find comments with estimations out of pre-planning # zisco 2021-09-30 19:50 from types import ClassMethodDescriptorType from jira import JIRA, Issue, Comment, exceptions import re import csv import xlsxwriter import logging from bisect import bisect_left def take_closest(myList, myNumber): """ Assumes myList is sorted. Returns closest value to myNumber. If two numbers are equally close, return the smallest number. """ pos = bisect_left(myList, myNumber) if pos == 0: return myList[0] if pos == len(myList): return myList[-1] before = myList[pos - 1] after = myList[pos] if after - myNumber < myNumber - before: return after else: return before def get_assignee(type, issue): isBuergerfrontend = False if type == 'Dev Backend': return 'reu9001' elif type == 'Dev Frontend': for component in issue.fields.components: isBuergerfrontend = component.name == "Bürgerfrontend" if isBuergerfrontend: return 'reu9001' return 'thh9001' elif type.startswith('Test-Anpassung'): return 'osm9005' def create_subtask(issue, parent, type, subtask_sp, estimation_remark): escaped = issue.fields.summary.replace('"', r'\"') logging.info(f'summary ~ \"{escaped}\"') try: subtask = jira_server.search_issues(f'summary ~ \"{type} {escaped}\"') if subtask: raise exceptions.JIRAError except exceptions.JIRAError: logging.info(f'Skip creating Sub-Task {type} for {issue} as it already exists') return issue_type = 'Sub-task' assignee = get_assignee(type, issue) description = 's. Story' if estimation_remark != "": description += "\nEstimation remark: {}".format(estimation_remark) issue_dict = { 'project': {'id': issue.fields.project.id}, 'summary': "[{}] {}".format(type, escaped), 'description': description, # issue.fields.description, 'issuetype': {'name': issue_type}, 'priority': {'name': issue.fields.priority.name}, 'parent': {'key': parent}, 'labels': issue.fields.labels, #'customfield_10022' - Story Points 'timetracking': {'originalEstimate': "{}d".format(subtask_sp)}, 'assignee': {'name': assignee} } new_issue = jira_server.create_issue(fields=issue_dict) # consider components existingComponents = [] for component in issue.fields.components: existingComponents.append({"name" : component.name}) new_issue.update(fields={"components": existingComponents}) #new_issue.update(priority={"name": issue.fields.priority.name}) logging.info('Created Sub-Task {}'.format(new_issue)) logging.basicConfig(filename='sub_task_generator.log', filemode='a', format='%(asctime)s - %(levelname)s: %(message)s', level=logging.INFO, encoding='utf-8') host = "https://jira.wien.gv.at/" pat = "OTAyMTM3MjY0MjE0Ovz6bu0ti1TlEdSegmmiWiJ2wGeD" sprint = 9 headers = JIRA.DEFAULT_OPTIONS["headers"].copy() headers["Authorization"] = f"Bearer {pat}" jira_server = JIRA(server=host, options={"headers": headers}) # use filter or single issue issues_Jql = jira_server.search_issues('filter=24707') #, expand='changelog') #issues_Jql = jira_server.search_issues('key in (DKARB-140)') #, expand='changelog') #issues_Jql = jira_server.search_issues('labels in (Refinement11012022) AND type not in (Bug) AND status not in (Done) and ("Story Points" is EMPTY or "Story Points" = 0)') #, expand='changelog') logging.info('Creating sub-tasks out of estimation of pre-planning sprint {}'.format(sprint)) xsp = lambda s: s or 0 issue: Issue for issue in issues_Jql: logging.info('Working on {}'.format(issue)) sp_sum = 0 # only Issues with empty (or 0) story-points sp = 0 if hasattr(issue.fields, 'customfield_10022'): sp = xsp(issue.fields.customfield_10022) if sp != 0: logging.info('Ignoring issue {} as there are Story-Points already omitted'.format(issue)) else: single_issue = jira_server.issue(issue.key) comments = single_issue.fields.comment.comments # Find all comments made by me on this issue. for comment in comments: comment_lines = comment.body.splitlines() if len(comment_lines) == 5: if comment_lines[0][:5] == "Dev B": for comment_line in comment_lines: if comment_line != "": # pos = comment_lines[2].find('NN') comment_line_parts = comment_line.split(':') # if 1. Test* != NN create a sub-task (if not existing) if len(comment_line_parts) == 2: if comment_line_parts[1] != u'\xa0': # empty string if (comment_line_parts[1][-2:] != 'NN') and (comment_line_parts[1][-1:] != '-'): # NN means NotNecessary comment_line_sub_parts = comment_line_parts[1].split(' (') if len(comment_line_sub_parts)>1: subtask_ph = float(comment_line_sub_parts[0]) * 0.5 estimation_remark = comment_line_sub_parts[1] else: if comment_line_sub_parts[0].strip().isnumeric(): subtask_ph = float(comment_line_sub_parts[0]) * 0.5 estimation_remark = "" else: if comment_line_sub_parts[0].strip() == "tbd": subtask_ph = take_closest([1,2,3,5,8,13,21,42], sp_sum/3) if comment_line_parts[0][:15] != "Test-Ausführung": create_subtask(issue, issue.key, comment_line_parts[0], subtask_ph, estimation_remark) sp_sum += subtask_ph / 0.5 # find corresponding sub-task # issuelinks.inwardIssue try: if sp != sp_sum: issue.update(fields={'customfield_10022': sp_sum}) logging.info("Estimated story-points changed to Sum(Sub-Tasks story-points): {}".format(sp_sum, sp_sum)) except exceptions.JIRAError: logging.error("Issue {} has no (visible) Story Points-attribute".format(issue.key)) finally: logging.info('Done {}'.format(issue.key)) #print('{}: {}, SP: {}'.format(issue.key, issue.fields.summary, sp)) #print('Summe {}: {}'.format(assignee, sp_sum)) #print(issues_Jql) # changelog = issues_Jql. # count = 0 # for history in changelog.histories: # for item in history.items: # if item.field == 'status': # if item.toString == "Reopened": # count = count + 1 # print(count)