class JIRAUtilities: # Translate state changes to JIRA transition values transition_ids = {'ToDo': '11', 'Prog': '21', 'Imped': '41', 'Done': '31'} user_story_dict_cloud = { #'project': 'SP', #'project': 'TTP', # Tsafi, 15 Jan 2020 'project': DEFAULT_JIRA_PARAMS[JIRA_INST]['PROJECT'], # Tsafi 3 Feb 2020 'issuetype': 'Story', 'summary': "Story 302", 'customfield_10322': [{ 'value': 'Dev Team 1' }] # Team field ********cloud } user_story_dict_vm = { #'project': 'SP', #'project': 'TTP', #Tsafi 14 Jan 2020 - temp change to work with TTP project already exists on local Jira 'project': DEFAULT_JIRA_PARAMS[JIRA_INST]['PROJECT'], # Tsafi 3 Feb 2020 'issuetype': 'Story', 'summary': "Story 302", #'customfield_10201': [{'value': 'Dev Team 1'}] # Team field vm 'customfield_10200': [{ 'value': 'Dev Team 1' }] # Tsafi 21 Jan 2020 } # 10120 is the 'Epic Name' and it's a must have epic_dict_cloud = { #'project': 'SP', #'project': 'TTP', # Tsafi, 15 Jan 2020 'project': DEFAULT_JIRA_PARAMS[JIRA_INST]['PROJECT'], # Tsafi 3 Feb 2020 #'issuetype': 'Epic', 'issuetype': { 'name': 'Epic' }, # Tsafi 15 Jan 2020 'summary': "Epic 1", 'customfield_10120': "Epic 1", # Epic name is the same as Epic summary ********cloud 'customfield_10322': [{ 'value': 'Dev Team 1' }] # Team field ********cloud } epic_dict_vm = { 'project': DEFAULT_JIRA_PARAMS[JIRA_INST]['PROJECT'], # Tsafi 3 Feb 2020 'issuetype': 'Epic', 'summary': "Epic 1", 'customfield_10103': "Epic 1", # Epic name is the same as Epic summary vm #'customfield_10102': "Epic 1", # Tsafi 14 Jan 2020 'customfield_10200': [{ 'value': 'Dev Team 1' }] # Tsafi 21 Jan 2020 - Team field vm } def __init__(self, instance_type): self.instance_type = instance_type if instance_type == 'cloud': # Using Nela's Jira cloud account #self.jira_inst = JIRA(basic_auth=('*****@*****.**', 'FiJBeI3H81sceRofBcY4E84E'), # options={'server': 'https://dr-agile.atlassian.net'}) # Using Tsafi's Jira cloud account #self.jira_inst = JIRA(basic_auth=('*****@*****.**', 'tDshA7M9zEhMkaiC13RA146E'), # options={'server': 'https://dr-agile.atlassian.net'}) self.jira_inst = JIRA( basic_auth=(DEFAULT_JIRA_PARAMS['CLOUD']['USER'], DEFAULT_JIRA_PARAMS['CLOUD']['PASS']), options={'server': DEFAULT_JIRA_PARAMS['CLOUD']['URL']}) else: # Using Nela's Jira local VM #self.jira_inst = JIRA(basic_auth=('nela.g', 'q1w2e3r4'), options={'server': 'http://192.168.56.101:8080'}) # Using Tsafi's Jira local VM #self.jira_inst = JIRA(basic_auth=('tsafrir.m', 'Sim1965'), options={'server': 'http://192.168.43.55:8080'}) #self.jira_inst = JIRA(basic_auth=('tsafrir.m', 'Sim1965'), options={'server': 'http://192.168.43.41:8080'}) #self.jira_inst = JIRA(basic_auth=('tsafrir.m', 'Sim1965'), options={'server': 'http://10.0.0.61:8080'}) self.jira_inst = JIRA( basic_auth=(DEFAULT_JIRA_PARAMS['LOCAL']['USER'], DEFAULT_JIRA_PARAMS['LOCAL']['PASS']), options={'server': DEFAULT_JIRA_PARAMS['LOCAL']['URL']}) def __del__(self): del self.jira_inst # Creation def create_epic(self, name, team): if self.instance_type == 'cloud': dict = self.epic_dict_cloud dict['customfield_10120'] = name # ********cloud dict['customfield_10322'] = [{'value': team.name}] # ********cloud else: dict = self.epic_dict_vm dict['customfield_10103'] = name # tsafi 21 Jan 2020 #dict['customfield_10102'] = name # Tsafi 14 Jan 2020 #dict['customfield_10201'] = {'value': team.name} dict['customfield_10200'] = { 'value': team.name } # Tsafi 21 Jan 2020 dict['summary'] = name epic = self.jira_inst.create_issue(fields=dict) return epic def create_user_story_with_epic(self, user_story_name, team, epic_key=None): if self.instance_type == 'cloud': dict = self.user_story_dict_cloud dict['customfield_10322'] = [{'value': team.name}] # ********cloud else: dict = self.user_story_dict_vm #dict['customfield_10201'] = {'value': team.name} dict['customfield_10200'] = { 'value': team.name } # Tsafi 21 Jan 2020 dict['summary'] = user_story_name user_story = self.jira_inst.create_issue(fields=dict) if epic_key: self.jira_inst.add_issues_to_epic(epic_key, [user_story.key]) # Note the performance impact, this is another fetch after # adding a story to epic user_story = self.jira_inst.issue(user_story.id) return user_story def create_list_of_epics(self, list_of_epics, team): print('\nCreating list of epics for team %s in JIRA' % team.name) for epic in list_of_epics: jira_epic = self.create_epic(epic.name, team) epic.key = jira_epic.key def create_list_of_user_stories(self, list_of_user_stories, team): print('\nCreating list of user stories for team %s in JIRA' % team.name) for user_story in list_of_user_stories: jira_user_story = self.create_user_story_with_epic( user_story.name, team, user_story.epic.key) user_story.key = jira_user_story.key # Create Sprint # Best would be to provide a board_id of a board that contains all issues def create_sprint(self, board_id, sprint_name, start_date, sprint_size): end_date = start_date + timedelta(days=sprint_size) start_date_str = start_date.strftime("%d/%b/%y %#I:%M %p") end_date_str = end_date.strftime("%d/%b/%y %#I:%M %p") print(start_date_str) print(end_date_str) new_sprint = self.jira_inst.create_sprint(sprint_name, board_id, startDate=start_date_str, endDate=end_date_str) print("Created new Sprint: name = %s, id = %s" % (new_sprint.name, new_sprint.id)) print("Start date %s, end date %s" % (new_sprint.startDate, new_sprint.endDate)) return new_sprint # Set up Sprints def start_sprint(self, sprint_id): print("Please start the sprint manually on a global board") key = input() def end_sprint(self, sprint_id): print("Please end the sprint manually on a global board") key = input() def add_issues_to_sprint(self, sprint_id, user_stories_keys): print("Adding %d issues to Sprint %d" % (len(user_stories_keys), sprint_id)) self.jira_inst.add_issues_to_sprint(sprint_id, user_stories_keys) def update_one_issue(self, issue_key, transition): print("Updating issue %s, moving to %s" % (issue_key, transition)) self.jira_inst.transition_issue(issue_key, self.transition_ids[transition]) def advance_time_by_one_day(self): if self.instance_type != 'cloud': #Tsafi 20 Jan 2020, change path for Tsafi's local VM #path = "C:\\Windows\\WinSxS\\amd64_openssh-client-components-onecore_31bf3856ad364e35_10.0.17763.1_none_f0c3262e74c7e35c\\ssh.exe [email protected] \"cd /home/nelkag/Simulator/misc; python /home/nelkag/Simulator/misc/advanceoneday.py\"" #path = "C:\\Windows\\System32\\OpenSSH\\ssh.exe [email protected] \"cd /home/tsafi/SAFESimulator; python3 ./advanceoneday.py\"" #path = "C:\\Windows\\System32\\OpenSSH\\ssh.exe [email protected] python3 /home/tsafi/SAFESimulator/advanceoneday.py" #path = "C:\\Windows\\System32\\OpenSSH\\ssh.exe [email protected] date" #path = "C:\\Windows\\Sysnative\\OpenSSH\\ssh.exe [email protected] python3 ./SAFESimulator/advanceoneday.py" #path = "C:\\Windows\\Sysnative\\OpenSSH\\ssh.exe [email protected] python3 ./SAFESimulator/advanceoneday.py" path = 'C:\\Windows\\Sysnative\\OpenSSH\\ssh.exe tsafi@' + LOCAL_JIRA_IP + ' python3 ./SAFESimulator/advanceoneday.py' print(path) os.system(path)
class JiraTool: def __init__(self): self.server = jira_url self.basic_auth = (usrer, password) self.jiraClinet = None def login(self): self.jiraClinet = JIRA(server=self.server, basic_auth=self.basic_auth) if self.jiraClinet != None: return True else: return False def findIssueById(self, issueId): if issueId: if self.jiraClinet == None: self.login() return self.jiraClinet.issue(issueId) else: return 'Please input your issueId' def deleteAllIssue(self, project): project_issues = self.jiraClinet.search_issues('project=' + project.name) for issue in project_issues: logging.info('delete issue %s' % (issue)) issue.delete() def deleteAllSprint(self, board): sprints = self.jiraClinet.sprints(board.id) for sprint in sprints: logging.info('delete sprint %s' % (sprint.name)) sprint.delete() def getProject(self, name): projects = self.jiraClinet.projects() #logging.info("get project %s" %(name)) for project in projects: #logging.info("project %s" %(project.name)) if (name == project.name): return project return None #return self.jiraClinet.create_project(key='SCRUM', name=name, assignee='yujiawang', type="Software", template_name='Scrum') def getBoard(self, project, name): boards = self.jiraClinet.boards() for board in boards: if (name == board.name): logging.info("board:%s id:%d" % (board.name, board.id)) return board return self.jiraClinet.create_board(name, [project.id]) def createSprint(self, board, name, startDate=None, endDate=None): logging.info("==== create sprint[%s] in board[%s] ====>" % (name, board.name)) sprint = self.jiraClinet.create_sprint(name, board.id, startDate, endDate) return sprint def getSprint(self, board_id, sprint_name): sprints = self.jiraClinet.sprints(board_id) for sprint in sprints: if sprint.name == sprint_name: return sprint return None def createEpicTask(self, project, sprint, summary, description, assignee, participant, duedate): issue_dict = { 'project': { 'key': project.key }, 'issuetype': { 'id': issuetypes['Epic'] }, 'customfield_10002': summary, # epic 名称 'summary': summary, 'description': description, "customfield_10004": sprint.id, # sprint 'assignee': { 'name': assignee }, 'customfield_10303': participant, 'customfield_10302': '2018-08-24T05:41:00.000+0800' } logging.info(duedate) logging.info(issue_dict) #juse for debug issue = self.jiraClinet.create_issue(issue_dict) self.jiraClinet.add_issues_to_sprint(sprint.id, [issue.raw['key']]) logging.info( "===> add epic task[%s key:%s] to sprint [%s]" % (issue.raw['fields']['summary'], issue.raw['key'], sprint.name)) #dumpIssue(issue) #juse for debug return issue def createTask(self, project, sprint, epic, summary, description, assignee, participant): issue_dict = { 'project': { 'key': project.key }, 'issuetype': { 'id': issuetypes['Task'] }, 'summary': summary, 'description': description, 'assignee': { 'name': assignee }, 'customfield_10303': participant } issue = self.jiraClinet.create_issue(issue_dict) logging.info("==> add task[%s key:%s] link epic [%s key: %s]" % (issue.raw['fields']['summary'], issue.raw['key'], epic.raw['fields']['summary'], epic.raw['key'])) self.jiraClinet.add_issues_to_epic(epic.id, [issue.raw['key']]) self.jiraClinet.add_issues_to_sprint(sprint.id, [issue.raw['key']]) return issue def createSubTask(self, project, parent, summary, description, assignee, participant): issue_dict = { 'project': { 'key': project.key }, 'parent': { 'key': parent.raw['key'] }, 'issuetype': { 'id': issuetypes['Sub-Task'] }, 'summary': summary, 'description': description, 'assignee': { 'name': assignee }, 'customfield_10303': participant } issue = self.jiraClinet.create_issue(issue_dict) logging.info("=> add sub task[%s key:%s] to task [%s key: %s]" % (issue.raw['fields']['summary'], issue.raw['key'], parent.raw['fields']['summary'], parent.raw['key'])) return issue
from jira import JIRA from jira.resources import Sprint import time from datetime import datetime, timedelta analytics_jira = JIRA(server="SERVER URL",\ basic_auth=("USERNAME", "PASSWORD")) teams = {"DS Sprint": 3, "DAO Sprint": 8} start_date = (time.strftime("%m/%d")) date = datetime.strptime(start_date, "%m/%d") modified_startdate = date + timedelta(days=46) new_start = datetime.strftime(modified_startdate, "%m/%d") modified_enddate = modified_startdate + timedelta(days=13) end_date = datetime.strftime(modified_enddate, "%m/%d") for the_key, the_value in teams.iteritems(): a = the_key b = the_value c = 42 d = '(' + str(new_start) + '-' + str(end_date) + ')' analytics_jira.create_sprint(name='{0} {1} {2}'.format(a, c, d), board_id='{0}'.format(b)) print("Success")
class JiraAPI(object): """Access Jira api using python-jira project.""" def __init__(self, user, passwd, logger): """Init JiraApi object and logger.""" self.logger = logger self.jira = JIRA(server=config.JIRA_HOST, basic_auth=(user, passwd)) def get_boards(self): """Get Jira boards list as json.""" self.logger.info("Getting Jira boards") json = {'boards': []} boards = self.jira.boards() for board in boards: json.get('boards').append({'id': board.id, 'name': board.name}) return json # ############### # ### Sprints ### # ############### def _get_board_sprints(self, board_id): """Get sprints of a board as json.""" self.logger.info("Getting board {} sprints".format(board_id)) json = {'sprints': []} sprints = self.jira.sprints(board_id, extended=True) for sprint in sprints: # self.logger.debug("Sprint content: {}".format(sprint.__dict__)) json.get('sprints').append({ 'id': sprint.id, 'key': sprint.id, 'name': sprint.name, 'start': sprint.startDate, 'end': sprint.endDate }) return json def search_sprint(self, board_key, sprint_name): """Search a sprint in a board and returns its info.""" self.logger.info("Searching board {} sprint '{}'".format( board_key, sprint_name)) found_sprint = None sprints = self._get_board_sprints(board_key) for sprint in sprints.get('sprints'): if (sprint.get('name') == sprint_name): found_sprint = sprint break return found_sprint def create_sprint(self, board_id, sprint_name, start, end): """Create a sprint in a board with given start and end dates. Dates must be in Jira format. """ self.logger.info("Creating sprint {}".format(sprint_name)) sprint = self.jira.create_sprint(name=sprint_name, board_id=board_id, startDate=start, endDate=end) # self.logger.debug("Created sprint content {}".format(sprint.__dict__)) return sprint.id def start_sprint(self, sprint_id, sprint_name, start, end): """Start a Jira sprint with given dates. *** Does not work because Jira API call fails with 'state' param. *** Dates must be in Jira format. """ self.logger.info("Starting sprint {} [Not working]".format(sprint_id)) # self.jira.update_sprint(sprint_id, name=sprint_name, startDate=start, endDate=end, state="active") def close_sprint(self, sprint_id, sprint_name, start, end): """Closes a Jira sprint with given dates. *** Does not work because Jira API call fails with 'state' param. *** Dates must be in Jira format. """ self.logger.info("Closing sprint {} [Not working]".format(sprint_id)) # self.jira.update_sprint(sprint_id, name=sprint_name, startDate=start, endDate=end, state=None) def delete_board_sprints(self, board_id): """Delete all sprints of a board.""" self.logger.info("Deleting board {} sprints".format(board_id)) sprints = self.jira.sprints(board_id, extended=False) for sprint in tqdm(sprints): self.logger.info("Deleting sprint {}".format(sprint.name)) sprint.delete() def _get_board_project_id(self, board_id): """Get the project id of a board.""" self.logger.debug("Getting project id of board {}".format(board_id)) project_id = None boards = self.jira.boards() for board in boards: # self.logger.debug("Comparing boards {}-{}".format(board.id, board_id)) if str(board.id) == str(board_id): # self.logger.debug("Found matching board {}".format(board.raw)) project_id = board.raw.get("filter").get("queryProjects").get( "projects")[0].get("id") break return project_id # #################### # ### User stories ### # #################### def _get_project_user_stories(self, project_key): """Get all user stories of a project.""" self.logger.info("Getting project {} user stories".format(project_key)) issues = self.jira.search_issues('project=' + project_key + ' and issuetype=' + config.JIRA_USER_STORY_TYPE, maxResults=200) return issues def delete_all_project_user_stories(self, project_key): """Delete all user stories of a project.""" self.logger.info( "Deleting project {} user stories".format(project_key)) issues = self._get_project_user_stories(project_key) for issue in tqdm(issues): self.logger.info("Deleting user story {}".format(issue)) issue.delete() def _create_user_story(self, project_id, subject, description, tags, points): """Create a user story with provided information.""" story_fields = { "project": project_id, "issuetype": config.JIRA_USER_STORY_TYPE, "summary": subject, "description": description, "labels": tags, config.JIRA_ESTIMATION_FIELD: points } created_story = self.jira.create_issue(fields=story_fields) self.logger.info("Created user story {} # {}".format( created_story.id, created_story.key)) # self.logger.debug("Created user story details: {}".format(created_story.__dict__)) return created_story def update_user_story_status(self, user_story_key, is_closed, status): """Update the status of a user story to Done or Not Done only if it's closed. A translation is made between the status given, which is the status in Taiga and the Jira status as defined in JIRA_USER_STORY_STATUS_DONE, JIRA_USER_STORY_STATUS_NOT_DONE and TAIGA_USER_STORY_STATUS_NOT_DONE constants of config.py file. """ self.logger.info( "Updating user story {} with Taiga status={} ({})".format( user_story_key, status, is_closed)) if is_closed: task_status = config.JIRA_USER_STORY_STATUS_DONE if status == config.TAIGA_USER_STORY_STATUS_NOT_DONE: task_status = config.JIRA_USER_STORY_STATUS_NOT_DONE self.jira.transition_issue(user_story_key, task_status) self.logger.info("Updated user story {} status to {}".format( user_story_key, task_status)) else: self.logger.warn( "Not updated user story {} beacuse is not closed: is_closed={}" .format(user_story_key, is_closed)) # ######################## # ### User story tasks ### # ######################## def _create_user_story_task(self, project_id, user_story_id, subject, description, finished_date): """Create a task inside a user story. The story task type is defined in config.py in JIRA_USER_STORY_TASK_TYPE constant. default is 'Sub-type'. """ self.logger.info("Creating user story task {}".format(subject)) created_task = self.jira.create_issue( project=project_id, parent={"id": user_story_id}, issuetype=config.JIRA_USER_STORY_TASK_TYPE, summary=subject) self.logger.info("Created user story task {} # {}".format( created_task.id, created_task.key)) # self.logger.debug("Created story task details: {}".format(created_task.__dict__)) return created_task def _update_task_status(self, task_key, status): """Update task status with Done or not Done status depending on incoming Taiga status. Taiga done status is defined in TAIGA_TASK_STATUS_DONE constant inside config.py file. Jira done and not done statuses are defined in JIRA_TASK_STATUS_DONE and JIRA_TASK_STATUS_NOT_DONE inside config.py file. """ self.logger.info( "Updating user story task {} with Taiga status={}".format( task_key, status)) task_status = config.JIRA_TASK_STATUS_DONE if status != config.TAIGA_TASK_STATUS_DONE: task_status = config.JIRA_TASK_STATUS_NOT_DONE self.jira.transition_issue(task_key, task_status) self.logger.info("Updated user story task {} status to {}".format( task_key, task_status)) # ####################### # ### Sprint creation ### # ####################### def _add_comment(self, issue_id, comment): """Add a comment to an issue.""" self.logger.debug("Adding comment to issue {}".format(issue_id)) self.jira.add_comment(issue_id, comment, visibility={'group': 'jira-users'}) def create_sprint_stories(self, board_id, sprint_id, user_stories): """Create user stories in a board and link them to a sprint. A json array with user story key, is_closed and status info is returned. User stories subtasks are also added to the story. User story tasks finished date and current taiga status are added as comments. User story finished date and current taiga status are added as comments. """ project_id = self._get_board_project_id(board_id) self.logger.info( "Creating user stories in project {} - sprint {}".format( project_id, sprint_id)) created_stories = [] for user_story in user_stories: self.logger.info("Creating user story {}".format( user_story.get("subject"))) created_story = self._create_user_story( project_id, user_story.get("subject"), user_story.get("description"), user_story.get("tags"), user_story.get("total_points")) self.logger.info("Adding user story {} to sprint {}".format( created_story.key, sprint_id)) self.jira.add_issues_to_sprint(sprint_id, [created_story.key]) for task in user_story.get("tasks"): created_task = self._create_user_story_task( project_id, created_story.id, task.get("subject"), task.get("description"), task.get("finished_date")) # Add as comment user story finished date self._add_comment( created_task.id, "Finished date: '{}'".format(task.get('finished_date'))) self._add_comment( created_task.id, "Taiga status: '{}'".format(task.get("status"))) # Update task status self._update_task_status(created_task.key, task.get("status")) created_stories.append({ "key": created_story.key, "is_closed": user_story.get("is_closed"), "status": user_story.get("status") }) # Add as comment user story finished date self._add_comment( created_story.id, "Finished date: '{}'".format(user_story.get('finish_date'))) self._add_comment( created_story.id, "Taiga status: '{}'".format(user_story.get("status"))) return created_stories def add_backlogs_stories(self, board_id, user_stories): """Add user stories to the backlog of the board. Taiga original status and backlog order are added as comments. """ project_id = self._get_board_project_id(board_id) self.logger.info( "Creating user stories in project {} backlog".format(project_id)) created_stories = [] for user_story in user_stories: self.logger.info("Creating user story {}".format( user_story.get("subject"))) created_story = self._create_user_story( project_id, user_story.get("subject"), user_story.get("description"), user_story.get("tags"), user_story.get("total_points")) created_stories.append({"key": created_story.key}) # Add as comment user story finished date self._add_comment( created_story.id, "Taiga status: '{}'".format(user_story.get("status"))) self._add_comment( created_story.id, "Taiga backlog order: '{}'".format( user_story.get("backlog_order"))) return created_stories