Пример #1
0
def send_tickets(grouped_by_groupd_id, project, login, password):   
   options = {"server": "https://jira.onap.org"}
   jira = JIRA(options, auth=(login, password))
   
   epic_dict = {
      'project': project,
      'summary': "The subsequent stories are generated per group id of outdated dependencies",
      'description': "Outdated dependencies",
      'customfield_10003': 'Outdated dependencies',
      'issuetype': {'name': 'Epic'},
   }
   epic_issue = jira.create_issue(epic_dict)

   for key in grouped_by_groupd_id:
      description = ""
      summary = "[automatically generated] Outdated dependency " + str(key)
      for i_key in grouped_by_groupd_id[key]:         
         description += "\n-= Take this result with a grain of salt, use your own judgment, and check for CVE =-\n"
         description += "\nartifact:         " + str(i_key['declared']['artifact_id'])
         description += "\npackage:          " + str(i_key['declared']['package'])
         description += "\ndeclared version: " + str(i_key['declared']['version'])
         description += "\nlatest   version: " + str(i_key['latest_version'])
         description += "\nuri checked:      " + str(i_key['uri'])
      
      issue = jira.create_issue(project=project, description=description, summary=summary, issuetype={'name': 'Story'})
      jira.add_issues_to_epic(epic_issue.id, [issue.key])
      print("created: " + str(issue.permalink()))
Пример #2
0
def add_issue(find, push_to_jira):
    eng = Engagement.objects.get(test=find.test)
    prod =  Product.objects.get(engagement= eng)
    jpkey = JIRA_PKey.objects.get(product=prod)
    jira_conf = jpkey.conf
    if push_to_jira:
        if 'Active' in find.status() and 'Verified' in find.status():
                jira = JIRA(server=jira_conf.url, basic_auth=(jira_conf.username, jira_conf.password))
                new_issue = jira.create_issue(project=jpkey.project_key, summary=find.title, description=find.long_desc(), issuetype={'name': 'Bug'}, priority={'name': jira_conf.get_priority(find.severity)})
                j_issue = JIRA_Issue(jira_id=new_issue.id, jira_key=new_issue, finding = find)
                j_issue.save()
                if jpkey.enable_engagement_epic_mapping:
                      epic = JIRA_Issue.objects.get(engagement=eng)
                      issue_list = [j_issue.jira_id,]
                      jira.add_issues_to_epic(epic_id=epic.jira_id, issue_keys=[str(j_issue.jira_id)], ignore_epics=True)
Пример #3
0
def test_jira_api(args):
    logger.info(str(args))
    if not isinstance(g.identity, AnonymousIdentity):
        raw_ua = request.headers.get('User-Agent') or ''
        ua = user_agents.parse(raw_ua)
        args['os'] = ua.os.family
        args['browser'] = ua.browser.family
        args['device'] = ua.device.family
        args['ua'] = raw_ua
    from jira import JIRA

    HOST = current_app.config['JIRA_HOST']
    BASIC_AUTH_USER = current_app.config['BASIC_AUTH_USER']
    BASIC_AUTH_PASSWORD = current_app.config['BASIC_AUTH_PASSWORD']
    PROJECT = current_app.config['PROJECT']
    EPIC = current_app.config['EPIC']
    # 权限
    jira = JIRA(HOST, basic_auth=(BASIC_AUTH_USER, BASIC_AUTH_PASSWORD))
    # 新建issue
    new_issue = jira.create_issue(project=PROJECT,
                                  summary=args['summary'],
                                  description=args.get('description', ''),
                                  issuetype={'name': args['type']})
    # 添加issue到epic
    jira.add_issues_to_epic(EPIC, [str(new_issue.key)])
    # 添加额外信息到comment
    display_list = ['summary', 'description', 'type', 'file']
    comment_body = ''.join(
        ['%s:%s \n' % (k, args[k]) for k in args if k not in display_list])
    jira.add_comment(new_issue.key, comment_body) if comment_body else ""
    # 添加附件
    data = request.files.getlist("file")
    current_app.logger.info(type(data))
    if data:
        for file_data in data:
            current_app.logger.info(file_data.filename)
            binary = file_data.stream.read()
            if binary:
                jira.add_attachment(issue=new_issue,
                                    attachment=binary,
                                    filename=str(uuid.uuid1()) + '.' +
                                    file_data.filename.split(".")[-1])
Пример #4
0
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)
Пример #5
0
class JiraOperations:
    def __init__(self, credentials):
        self.epics_dict = {}
        self.options = {"server": SERVER_ADDRESS}
        self.jira = JIRA(self.options, basic_auth=credentials)
        self.get_epics()

    def get_epics(self):
        found_issues = self.jira.search_issues(
            'project=10031275_mPAD_ITV and type = Epic and summary ~ "Timesheet "'
        )
        for issue in found_issues:
            self.epics_dict[issue.fields.summary] = issue.id

    def get_epic_id(self):
        today = datetime.datetime.now()
        month_name = today.strftime("%B")[:3]
        year = str(today.year)[2:]
        timesheet_string = 'Timesheet ' + month_name + year
        return self.epics_dict[timesheet_string]

    def find_issue_key(self, summary):
        found_issues = self.jira.search_issues(
            'project=10031275_mPAD_ITV and type = Story and reporter = '
            'currentUser() and status != done')
        for issue in found_issues:
            if issue.fields.summary.upper().strip() == summary:
                return issue

    def is_story_created(self, summary):
        found_issues = self.jira.search_issues(
            'project=10031275_mPAD_ITV and type = Story and reporter = '
            'currentUser() and status != done')
        for issue in found_issues:
            if issue.fields.summary.upper().strip() == summary:
                return True
        return False

    def get_stories_created_today(self):
        issue_list = []
        work_time = datetime.timedelta()
        break_time = datetime.timedelta()
        found_issues = self.jira.search_issues(
            'project=10031275_mPAD_ITV and type = Story and reporter = '
            'currentUser() and createdDate > startOfDay() and (status = open OR '
            'status = Done)')
        for issue in found_issues:
            issue_list.append(
                "Key: %s, status: %s, summary: %s, created: %s, duration: %s, description: %s"
                % (issue.key, issue.fields.status, issue.fields.summary,
                   date_convert(issue.fields.created),
                   timedelta_to_str(
                       calculate_duration(issue.fields.created,
                                          issue.fields.resolutiondate)),
                   issue.fields.description))
            if issue.fields.summary.upper().strip() == 'WORK':
                work_time = work_time + calculate_duration(
                    issue.fields.created, issue.fields.resolutiondate)
            elif issue.fields.summary.upper().strip() == 'BREAK':
                break_time = break_time + calculate_duration(
                    issue.fields.created, issue.fields.resolutiondate)

        total_work_today = work_time - break_time
        issue_list.append("Total work time today: %s" %
                          timedelta_to_str(total_work_today))
        return issue_list

    def create_ticket(self, summary, description=''):
        created_issue = self.jira.create_issue(project=PROJECT_KEY,
                                               summary=summary,
                                               description=description,
                                               issuetype={'name': 'Story'},
                                               labels=[TT_VERSION])
        self.jira.add_issues_to_epic(self.get_epic_id(), [created_issue.key])
        return created_issue.key

    def close_ticket(self, ticket_number):
        self.jira.transition_issue(ticket_number,
                                   'done',
                                   fields={'resolution': {
                                       'name': 'Done'
                                   }})

    def description_init(self, summary):
        if self.is_story_created(summary):
            issue = self.find_issue_key(summary)
            if issue.fields.description is not None:
                return issue.fields.description
            else:
                return ''
        else:
            return ''

    def update_description(self, summary, description):
        if self.is_story_created(summary):
            issue_to_update = self.find_issue_key(summary)
            issue_to_update.update(fields={'description': description})
            return True
        else:
            return False

    def get_week_statistics(self):
        week_statistics = []
        work_story = 0
        break_story = 0
        work_time = datetime.timedelta()
        break_time = datetime.timedelta()
        balance = datetime.timedelta()
        work_expected = datetime.timedelta(hours=EXPECTED_HOURS,
                                           minutes=EXPECTED_MINUTES)
        found_issues = self.jira.search_issues(
            'project=10031275_mPAD_ITV and type = Story and reporter = '
            'currentUser() and createdDate > startOfWeek() and (status = open OR '
            'status = Done)')
        for issue in found_issues:
            issue_summary = issue.fields.summary.upper().strip()
            if issue_summary == 'WORK':
                work_time = work_time + calculate_duration(
                    issue.fields.created, issue.fields.resolutiondate)
                work_story += 1
            elif issue_summary == 'BREAK':
                break_time = break_time + calculate_duration(
                    issue.fields.created, issue.fields.resolutiondate)
                break_story += 1
            elif issue_summary.split()[0] == 'BAL':
                hours = int(issue_summary.split()[3])
                minutes = int(issue_summary.split()[5])
                if issue_summary.split()[2] == '+':
                    balance = balance + datetime.timedelta(hours=hours,
                                                           minutes=minutes)
                    work_time = work_time + datetime.timedelta(hours=hours,
                                                               minutes=minutes)
                elif issue_summary.split()[2] == '-':
                    balance = balance + datetime.timedelta(hours=hours,
                                                           minutes=minutes)
                    work_time = work_time - datetime.timedelta(hours=hours,
                                                               minutes=minutes)
                else:
                    messagebox.showinfo(
                        "Error", "Found story with unknown sign: %s." %
                        issue_summary.split()[2])
            else:
                messagebox.showinfo(
                    "Error", "Found story with unknown summary: %s." %
                    issue.fields.summary)
        total_work_week = work_time - break_time
        total_work_expected = work_story * work_expected
        if total_work_expected - total_work_week >= datetime.timedelta():
            time_left_today = timedelta_to_str(total_work_expected -
                                               total_work_week)
        else:
            time_left_today = '-' + timedelta_to_str(total_work_week -
                                                     total_work_expected)
        week_statistics.append(
            "Total work stories created this week: %d, total break stories created this week: %d."
            % (work_story, break_story))
        week_statistics.append("Total work time expected this week: %s" %
                               timedelta_to_str(total_work_expected))
        week_statistics.append(
            "Total worked time this week: %s, including %s from balance stories"
            % (timedelta_to_str(total_work_week), str(balance)))
        week_statistics.append("Left time for today: %s" % time_left_today)
        return week_statistics

    def start_work(self):
        if not self.is_story_created('WORK'):
            self.create_ticket('WORK')
            return True
        else:
            return False

    def stop_work(self):
        if self.is_story_created('WORK'):
            issue_key = self.find_issue_key('WORK')
            self.close_ticket(issue_key)
            return True
        else:
            return False

    def start_break(self, description):
        if not self.is_story_created('BREAK'):
            self.create_ticket('BREAK', description)
            return True
        else:
            return False

    def stop_break(self):
        if self.is_story_created('BREAK'):
            issue_key = self.find_issue_key('BREAK')
            self.close_ticket(issue_key)
            return True
        else:
            return False
Пример #6
0
class JiraWrapper:
    JIRA_REQUEST = 'project={} AND labels in ({})'

    def __init__(self,
                 args,
                 url,
                 user,
                 password,
                 jira_project,
                 assignee,
                 check_functional_errors,
                 check_performance_degradation,
                 check_missed_thresholds,
                 performance_degradation_rate,
                 missed_thresholds_rate,
                 issue_type='Bug',
                 labels=None,
                 watchers=None,
                 jira_epic_key=None):
        self.valid = True
        self.args = args
        self.url = url
        self.password = password
        self.user = user
        self.check_functional_errors = check_functional_errors
        self.check_performance_degradation = check_performance_degradation
        self.check_missed_thresholds = check_missed_thresholds
        self.performance_degradation_rate = performance_degradation_rate
        self.missed_thresholds_rate = missed_thresholds_rate
        try:
            self.connect()
        except Exception:
            self.valid = False
            return
        self.projects = [project.key for project in self.client.projects()]
        self.project = jira_project
        if self.project not in self.projects:
            self.client.close()
            self.valid = False
            return
        self.assignee = assignee
        self.issue_type = issue_type
        self.labels = list()
        if labels:
            self.labels = [label.strip() for label in labels.split(",")]
        self.watchers = list()
        if watchers:
            self.watchers = [
                watcher.strip() for watcher in watchers.split(",")
            ]
        self.jira_epic_key = jira_epic_key
        self.client.close()

    def connect(self):
        self.client = JIRA(self.url, basic_auth=(self.user, self.password))

    def create_issue(self,
                     title,
                     priority,
                     description,
                     issue_hash,
                     attachments=None,
                     get_or_create=True,
                     additional_labels=None):
        _labels = [issue_hash]
        if additional_labels and isinstance(additional_labels, list):
            _labels.extend(additional_labels)
        _labels.extend(self.labels)
        issue_data = {
            'project': {
                'key': self.project
            },
            'summary': title,
            'description': description,
            'issuetype': {
                'name': self.issue_type
            },
            'assignee': {
                'name': self.assignee
            },
            'priority': {
                'name': priority
            },
            'labels': _labels
        }
        jira_request = self.JIRA_REQUEST.format(issue_data["project"]["key"],
                                                issue_hash)
        if get_or_create:
            issue, created = self.get_or_create_issue(jira_request, issue_data)
        else:
            issue = self.post_issue(issue_data)
            created = True
        if attachments:
            for attachment in attachments:
                if 'binary_content' in attachment:
                    self.add_attachment(
                        issue.key,
                        attachment=attachment['binary_content'],
                        filename=attachment['message'])
        for watcher in self.watchers:
            self.client.add_watcher(issue.id, watcher)
        if self.jira_epic_key:
            self.client.add_issues_to_epic(self.jira_epic_key, [issue.id])
        return issue, created

    def add_attachment(self, issue_key, attachment, filename=None):
        issue = self.client.issue(issue_key)
        for _ in issue.fields.attachment:
            if _.filename == filename:
                return
        self.client.add_attachment(issue, attachment, filename)

    def post_issue(self, issue_data):
        issue = self.client.create_issue(fields=issue_data)
        print("Issue " + issue.key + " created." + " Description - " +
              issue_data['summary'])
        return issue

    def get_or_create_issue(self, search_string, issue_data):
        issuetype = issue_data['issuetype']['name']
        created = False
        jira_results = self.client.search_issues(search_string)
        issues = []
        for each in jira_results:
            if each.fields.summary == issue_data.get('summary', None):
                issues.append(each)
        if len(issues) == 1:
            issue = issues[0]
            if len(issues) > 1:
                print('  more then 1 issue with the same summary')
            else:
                print(issuetype + 'issue already exists:' + issue.key)
        else:
            issue = self.post_issue(issue_data)
            created = True
        return issue, created

    @staticmethod
    def create_functional_error_description(error, arguments):
        title = "Functional error in test: " + str(arguments['simulation']) + ". Request \"" \
                + str(error['Request name']) + "\"."
        description = "{panel:title=" + title + \
                      "|borderStyle=solid|borderColor=#ccc|titleBGColor=#23b7c9|bgColor=#d7f0f3} \n"
        description += "h3. Request description\n"
        if error['Request name']:
            description += "*Request name*: " + error['Request name'] + "\n"
        if error['Method']:
            description += "*HTTP Method*: " + error['Method'] + "\n"
        if error['Request URL']:
            description += "*Request URL*: " + error['Request URL'] + "\n"
        if error['Request_params'] and str(error['Request_params']) != " ":
            description += "*Request params*: " + str(
                error['Request_params'])[2:-2].replace(" ", "\n") + "\n"
        if error['Request headers']:
            description += "*Request headers*: {code}"
            headers = str(error['Request headers']).replace(": ", ":")
            for header in headers.split(" "):
                description += header + "\n"
            description += "{code}\n"
        description += "---- \n h3. Error description\n"
        if error['Error count']:
            description += "*Error count*: " + str(
                error['Error count']) + ";\n"
        if error['Response code']:
            description += "*Response code*: " + error['Response code'] + ";\n"
        if error['Error_message']:
            description += "*Error message*: {color:red}" + str(
                error['Error_message']) + "{color}\n"
        if error['Response']:
            if len(str(error['Response'])) < 55000:
                description += "---- \n h3. Response body {code:html}" + str(
                    error['Response']) + "{code}"
            else:
                description += "---- \n See response body attached.\n"
        description += "{panel}"
        return description

    @staticmethod
    def get_functional_error_hash_code(error, arguments):
        error_str = arguments['simulation'] + "_" + error['Request URL'] + "_" + str(error['Error_message']) + "_" \
                    + error['Request name']
        return hashlib.sha256(error_str.strip().encode('utf-8')).hexdigest()

    @staticmethod
    def create_performance_degradation_description(
            performance_degradation_rate, compare_with_baseline, arguments):
        title = "Performance degradation in test: " + str(
            arguments['simulation'])
        description = "{panel:title=" + title + \
                      "|borderStyle=solid|borderColor=#ccc|titleBGColor=#23b7c9|bgColor=#d7f0f3} \n"
        description += "{color:red}" + "Test performance degradation is {}% compared to the baseline."\
            .format(performance_degradation_rate) + "{color} \n"
        description += "h3. The following requests are slower than baseline:\n"
        for request in compare_with_baseline:
            description += "\"{}\" reached {} ms by {}. Baseline {} ms.\n".format(
                request['request_name'], request['response_time'],
                arguments['comparison_metric'], request['baseline'])
        description += "{panel}"
        return description

    @staticmethod
    def create_missed_thresholds_description(missed_threshold_rate,
                                             compare_with_thresholds,
                                             arguments):
        title = "Missed thresholds in test: " + str(arguments['simulation'])
        description = "{panel:title=" + title + \
                      "|borderStyle=solid|borderColor=#ccc|titleBGColor=#23b7c9|bgColor=#d7f0f3} \n"
        description += "{color:red}" + "Percentage of requests exceeding the threshold was {}%." \
            .format(missed_threshold_rate) + "{color} \n"
        for color in ['yellow', 'red']:
            colored = False
            for th in compare_with_thresholds:
                if th['threshold'] == color:
                    if not colored:
                        description += f"h3. The following {color} thresholds were exceeded:\n"
                        colored = True
                    appendage = calculate_appendage(th['target'])
                    description += f"\"{th['request_name']}\" {th['target']}{appendage} " \
                                   f"with value {th['metric']}{appendage} " \
                                   f"exceeded threshold of {th[color]}{appendage}\n"
        description += "{panel}"
        return description

    def report_errors(self, aggregated_errors):
        for error in aggregated_errors:
            issue_hash = self.get_functional_error_hash_code(
                aggregated_errors[error], self.args)
            title = "Functional error in test: " + str(self.args['simulation']) + ". Request \"" \
                    + str(aggregated_errors[error]['Request name']) + "\" failed with error message: " \
                    + str(aggregated_errors[error]['Error_message'])[0:100]
            description = self.create_functional_error_description(
                aggregated_errors[error], self.args)
            if len(str(aggregated_errors[error]['Response'])) < 55000:
                self.create_issue(title, 'Major', description, issue_hash)
            else:
                content = io.StringIO()
                content.write(str(aggregated_errors[error]['Response']))
                attachment = {
                    "binary_content": content,
                    "message": "response_body.txt"
                }
                self.create_issue(title, 'Major', description, issue_hash,
                                  [attachment])

    def report_performance_degradation(self, performance_degradation_rate,
                                       compare_with_baseline):
        issue_hash = hashlib.sha256("{} performance degradation".format(
            self.args['simulation']).strip().encode('utf-8')).hexdigest()
        title = "Performance degradation in test: " + str(
            self.args['simulation'])
        description = self.create_performance_degradation_description(
            performance_degradation_rate, compare_with_baseline, self.args)
        self.create_issue(title, 'Major', description, issue_hash)

    def report_missed_thresholds(self, missed_threshold_rate,
                                 compare_with_thresholds):
        issue_hash = hashlib.sha256("{} missed thresholds".format(
            self.args['simulation']).strip().encode('utf-8')).hexdigest()
        title = "Missed thresholds in test: " + str(self.args['simulation'])
        description = self.create_missed_thresholds_description(
            missed_threshold_rate, compare_with_thresholds, self.args)
        self.create_issue(title, 'Major', description, issue_hash)
Пример #7
0
def create_issues_in_jira(*, issue_dicts, server_url, basic_auth, project_name,
                          board_name, assignee_key, components, epic_link,
                          max_results):
    jira = JIRA(server=server_url, basic_auth=basic_auth)

    def get_board(board_name):
        for board in jira.boards():
            if board.name == board_name:
                return board
        return None

    def get_project(project_name):
        for project in jira.projects():
            if project.name == project_name:
                return project
        return None

    project = get_project(project_name)
    if not project:
        raise Exception('project not found: \'{}\''.format(board_name))

    board = get_board(board_name)
    if not board:
        raise Exception('board not found: \'{}\''.format(board_name))

    component_ids = None
    if components is not None:
        component_objs = jira.project_components(project)

        def get_component(component_name):
            for component_obj in component_objs:
                if component_obj.name == component_name:
                    return component_obj
            return None

        component_ids = []
        for component in components:
            component_obj = get_component(component)
            if component_obj is None:
                raise Exception(
                    'component not found: \'{}\''.format(component))
            component_ids.append(component_obj.id)

    if epic_link:
        search_result = jira.search_issues(
            'summary ~ "{}"'.format(f'\\"{epic_link}\\"'))
        if len(search_result) == 0:
            raise Exception('epic not found: \'{}\''.format(epic_link))
        elif len(search_result) > 1:
            raise Exception(
                'more than one epic found for name \'{}\''.format(epic_link))
        epic = search_result[0]
    else:
        epic = None

    create_issues_results = []

    def _create_issue(issue_dict, parent=None):
        fields_list = [{
            'project': {
                'key': project.key
            },
            'summary': issue_dict['summary'],
            'description': issue_dict['description'],
            'issuetype': {
                'name': 'Task' if parent is None else 'Sub Task'
            },
        }]
        if parent is None:
            fields_list[0]['assignee'] = {
                'name':
                issue_dict['assignee']
                if issue_dict['assignee'] else assignee_key
            }
        if component_ids is not None:
            fields_list[0]['components'] = [{
                'id': component_id
            } for component_id in component_ids]
        if parent is not None:
            fields_list[0]['parent'] = {'key': parent.key}
        results = jira.create_issues(fields_list)
        assert len(results) == 1
        result = results[0]
        issue_obj = result['issue']
        for sub_issue_dict in issue_dict['sub_issues']:
            _create_issue(sub_issue_dict, issue_obj)
        create_issues_results.append(
            dict(issue_dict=issue_dict, issue_obj=issue_obj))

    for issue_dict in issue_dicts:
        _create_issue(issue_dict)

    if epic is not None:
        jira.add_issues_to_epic(epic.id, [
            create_issues_result['issue_obj'].key
            for create_issues_result in create_issues_results if
            create_issues_result['issue_obj'].fields.issuetype.name == 'Task'
        ])

    issues_to_add_to_sprint = [
        create_issues_result['issue_obj'].key
        for create_issues_result in create_issues_results
        if create_issues_result['issue_dict'].get('add_to_sprint', False)
    ]
    if issues_to_add_to_sprint:
        sprints = jira.sprints(board.id,
                               extended=['startDate', 'endDate'],
                               maxResults=max_results)
        if len(sprints) == 0:
            raise Exception('There\'s no open sprint')
        last_sprint = sprints[-1]
        jira.add_issues_to_sprint(last_sprint.id, issues_to_add_to_sprint)
Пример #8
0
def update_or_create_jira_issue(study_id, user_token, is_curator):
    try:
        params = app.config.get('JIRA_PARAMS')
        user_name = params['username']
        password = params['password']

        updated_studies = []
        try:
            jira = JIRA(options=options, basic_auth=(user_name, password))
        except:
            return False, 'Could not connect to JIRA server, incorrect username or password?', updated_studies

        # Get the MetaboLights project
        mtbls_project = jira.project(project)

        studies = [study_id]
        if not study_id and is_curator:
            studies = get_all_studies(user_token)

        for study in studies:
            study_id = study[0]
            user_name = study[1]
            release_date = study[2]
            update_date = study[3]
            study_status = study[4]
            curator = study[5]
            issue = []
            summary = None

            # Get an issue based on a study accession search pattern
            search_param = "project='" + mtbls_project.key + "' AND summary  ~ '" + study_id + " \\\-\\\ 20*'"
            issues = jira.search_issues(search_param)  # project = MetaboLights AND summary ~ 'MTBLS121 '
            new_summary = study_id + ' - ' + release_date.replace('-', '') + ' - ' + \
                          study_status + ' (' + user_name + ')'
            try:
                if issues:
                    issue = issues[0]
                else:
                    if study_status == 'Submitted':
                        logger.info("Could not find Jira issue for " + search_param)
                        print("Creating new Jira issue for " + search_param)
                        issue = jira.create_issue(project=mtbls_project.key, summary='MTBLS study - To be updated',
                                                  description='Created by API', issuetype={'name': 'Story'})
                    else:
                        continue  # Only create new cases if the study is in status Submitted
            except Exception:  # We could not find or create a Jira issue
                    continue

            summary = issue.fields.summary  # Follow pattern 'MTBLS123 - YYYYMMDD - Status'
            try:
                assignee = issue.fields.assignee.name
            except:
                assignee = ""

            valid_curator = False
            jira_curator = ""
            if curator:
                if curator.lower() == 'mark':
                    jira_curator = 'mwilliam'
                    valid_curator = True
                elif curator.lower() == 'keeva':
                    jira_curator = 'keeva'
                    valid_curator = True
            else:
                jira_curator = ""

            # Release date or status has changed, or the assignee (curator) has changed
            if summary.startswith('MTBLS') and (summary != new_summary or assignee != jira_curator):

                # Add "Curation" Epic
                issues_to_add = [issue.key]
                jira.add_issues_to_epic(curation_epic, issues_to_add)  # Add the Curation Epic
                labels = maintain_jira_labels(issue, study_status, user_name)

                # Add a comment to the issue.
                comment_text = 'Status ' + study_status + '. Database update date ' + update_date
                jira.add_comment(issue, comment_text)

                # Change the issue's summary, comments and description.
                issue.update(summary=new_summary, fields={"labels": labels}, notify=False)

                if valid_curator:  # ToDo, what if the curation log is not up to date?
                    issue.update(assignee={'name': jira_curator})

                updated_studies.append(study_id)
                logger.info('Updated Jira case for study ' + study_id)
                print('Updated Jira case for study ' + study_id)
    except Exception:
        return False, 'Update failed', updated_studies
    return True, 'Ticket(s) updated successfully', updated_studies
Пример #9
0
class JiraWrapper(object):
    JIRA_REQUEST = 'project={} AND labels in ({})'

    def __init__(self,
                 url,
                 user,
                 password,
                 project,
                 assignee,
                 issue_type='Bug',
                 labels=None,
                 watchers=None,
                 jira_epic_key=None):
        self.valid = True
        self.url = url
        self.password = password
        self.user = user
        try:
            self.connect()
        except:
            self.valid = False
            return
        self.projects = [project.key for project in self.client.projects()]
        self.project = project.upper()
        print(self.projects)
        if self.project not in self.projects:
            self.client.close()
            self.valid = False
            return
        self.assignee = assignee
        self.issue_type = issue_type
        self.labels = list()
        if labels:
            self.labels = [label.strip() for label in labels.split(",")]
        self.watchers = list()
        if watchers:
            self.watchers = [
                watchers.strip() for watchers in watchers.split(",")
            ]
        self.jira_epic_key = jira_epic_key
        self.client.close()

    def connect(self):
        self.client = JIRA(self.url, basic_auth=(self.user, self.password))

    def markdown_to_jira_markdown(self, content):
        return content.replace("###", "h3.").replace("**", "*")

    def create_issue(self,
                     title,
                     priority,
                     description,
                     issue_hash,
                     attachments=None,
                     get_or_create=True,
                     additional_labels=None):
        description = self.markdown_to_jira_markdown(description)
        _labels = [issue_hash]
        if additional_labels and isinstance(additional_labels, list):
            _labels.extend(additional_labels)
        _labels.extend(self.labels)
        issue_data = {
            'project': {
                'key': self.project
            },
            'summary': re.sub('[^A-Za-z0-9//\. _]+', '', title),
            'description': description,
            'issuetype': {
                'name': self.issue_type
            },
            'assignee': {
                'name': self.assignee
            },
            'priority': {
                'name': priority
            },
            'labels': _labels
        }
        jira_request = self.JIRA_REQUEST.format(issue_data["project"]["key"],
                                                issue_hash)
        if get_or_create:
            issue, created = self.get_or_create_issue(jira_request, issue_data)
        else:
            issue = self.post_issue(issue_data)
            created = True
        if attachments:
            for attachment in attachments:
                if 'binary_content' in attachment:
                    self.add_attachment(
                        issue.key,
                        attachment=attachment['binary_content'],
                        filename=attachment['message'])
        for watcher in self.watchers:
            self.client.add_watcher(issue.id, watcher)
        if self.jira_epic_key:
            self.client.add_issues_to_epic(self.jira_epic_key, [issue.id])
        return issue, created

    def add_attachment(self, issue_key, attachment, filename=None):
        issue = self.client.issue(issue_key)
        for _ in issue.fields.attachment:
            if _.filename == filename:
                return
        self.client.add_attachment(issue, attachment, filename)

    def post_issue(self, issue_data):
        print(issue_data)
        issue = self.client.create_issue(fields=issue_data)
        logging.info(
            f'  \u2713 {issue_data["issuetype"]["name"]} issue was created: {issue.key}'
        )
        return issue

    def get_or_create_issue(self, search_string, issue_data):
        issuetype = issue_data['issuetype']['name']
        created = False
        jira_results = self.client.search_issues(search_string)
        issues = []
        for each in jira_results:
            if each.fields.summary == issue_data.get('summary', None):
                issues.append(each)
        if len(issues) == 1:
            issue = issues[0]
            if len(issues) > 1:
                print('  more then 1 issue with the same summary')
            else:
                print(f'  {issuetype} issue already exists: {issue.key}')
        else:
            issue = self.post_issue(issue_data)
            created = True
        return issue, created
Пример #10
0
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
Пример #11
0
def update_or_create_jira_issue(user_token, is_curator):
    try:
        params = app.config.get('JIRA_PARAMS')
        user_name = params['username']
        password = params['password']
        default_curator = 'metabolights-api'

        updated_studies = []
        try:
            jira = JIRA(options=options, basic_auth=(user_name, password))
        except:
            return False, 'Could not connect to JIRA server, incorrect username or password?', updated_studies

        # Get the MetaboLights project
        mtbls_project = jira.project(project)

        if is_curator:
            studies = get_all_studies(user_token)

        for study in studies:
            study_id = None
            user_name = None
            release_date = None
            update_date = None
            study_status = None
            curator = None
            status_change = None
            curation_due_date = None

            try:
                study_id = safe_str(study[0])
                user_name = safe_str(study[1])
                release_date = safe_str(study[2])
                update_date = safe_str(study[3])
                study_status = safe_str(study[4])
                curator = safe_str(study[5])
                status_change = safe_str(study[6])
                curation_due_date = safe_str(study[7])
            except Exception as e:
                logger.error(str(e))
            issue = []
            summary = None

            # date is 'YYYY-MM-DD HH24:MI'
            due_date = status_change[:10]

            logger.info('Checking Jira ticket for ' + study_id + '. Values: ' +
                        user_name + '|' + release_date + '|' + update_date + '|' + study_status + '|' +
                        curator + '|' + status_change + '|' + due_date)

            # Get an issue based on a study accession search pattern
            search_param = "project='" + mtbls_project.key + "' AND summary  ~ '" + study_id + " \\\-\\\ 20*'"
            issues = jira.search_issues(search_param)  # project = MetaboLights AND summary ~ 'MTBLS121 '
            new_summary = study_id + ' - ' + release_date.replace('-', '') + ' - ' + \
                          study_status + ' (' + user_name + ')'
            try:
                if issues:
                    issue = issues[0]
                else:
                    if study_status == 'Submitted' or study_status == 'In Curation':
                        logger.info("Could not find Jira issue for " + search_param)
                        print("Creating new Jira issue for " + search_param)
                        issue = jira.create_issue(project=mtbls_project.key, summary='MTBLS study - To be updated',
                                                  description='Created by API', issuetype={'name': 'Story'})
                    else:
                        continue  # Only create new cases if the study is in status Submitted/In Curation
            except:  # We could not find or create a Jira issue.
                continue

            summary = issue.fields.summary  # Follow pattern 'MTBLS123 - YYYYMMDD - Status'

            if not summary.startswith('MTBLS'):
                continue  # Skip all cases that are not related the study accession numbers

            try:
                assignee = issue.fields.assignee.name
            except:
                assignee = ""

            assignee_changed = False
            valid_curator = False
            jira_curator = ""
            if curator:
                if curator.lower() == 'mark':
                    jira_curator = 'mwilliam'
                    valid_curator = True
                elif curator.lower() == 'pamela':
                    jira_curator = 'ppruski'
                    valid_curator = True
                elif curator.lower() == 'xuefei' or curator.lower() == 'reza' or curator.lower() == 'keeva':
                    jira_curator = default_curator  # We do not have a current curation listed in the log
                    valid_curator = True

                assignee_changed = True if assignee != jira_curator else False
            else:
                jira_curator = ""

            if not status_change:
                status_change = "No status changed date reported"
            # Release date or status has changed, or the assignee (curator) has changed

            summary_changed = True if summary != new_summary else False
            curator_update = True if assignee != default_curator and jira_curator != default_curator else False
            if assignee_changed or summary_changed:

                # Add "Curation" Epic
                issues_to_add = [issue.key]
                jira.add_issues_to_epic(curation_epic, issues_to_add)  # Add the Curation Epic
                labels = maintain_jira_labels(issue, study_status, user_name)

                # Add a comment to the issue.
                comment_text = 'Current status ' + study_status + '. Status last changed date ' + status_change + \
                               '. Curation due date ' + due_date + '. Database update date ' + update_date
                if jira_curator == default_curator:
                    comment_text = comment_text + '. Default curator has been changed from "' \
                                   + curator + '" to "' + default_curator + '"'
                if assignee_changed:
                    comment_text = comment_text + '. Curator in Jira changed from "' + assignee + '" to "' + jira_curator + '"'

                if summary_changed:
                    comment_text = comment_text + '. Summary in Jira changed from "' + summary + '" to "' + new_summary + '"'

                jira.add_comment(issue, comment_text)

                # Change the issue's summary, comments and description.
                issue.update(summary=new_summary, fields={"labels": labels}, notify=False)

                # if valid_curator:  # ToDo, what if the curation log is not up to date?
                issue.update(assignee={'name': jira_curator}, notify=False)

                updated_studies.append(study_id)
                logger.info('Updated Jira case for study ' + study_id)
                print('Updated Jira case for study ' + study_id)
    except Exception as e:
        logger.error("Jira updated failed for " + study_id + ". " + str(e))
        return False, 'Update failed: ' + str(e), str(study_id)
    return True, 'Ticket(s) updated successfully', updated_studies
Пример #12
0
class DonburiJira:
    def __init__(self, server, project, auth_user, auth_key):
        self.project = project
        self.issue_url_base = server.rstrip("/") + '/browse/'
        self.jira = JIRA({'server': server}, basic_auth=(auth_user, auth_key))

    def jira_instance(self):
        return self.jira

    def create_issue(self, account_id, summary, description, issuetype='Task'):
        issue = self.jira.create_issue(project=self.project,
                                       summary=summary,
                                       description=description,
                                       assignee={'accountId': account_id},
                                       issuetype={'name': issuetype})
        return issue

    def add_to_epic(self, issue, epic_id):
        epic = self.jira.issue(epic_id)
        if epic == None:
            return False

        # add ticket to epic
        self.jira.add_issues_to_epic(epic_id=epic_id, issue_keys=[issue.key])

        # take over labels from epic
        labels = epic.fields.labels
        issue.fields.labels.extend(labels)
        issue.update(fields={"labels": issue.fields.labels})
        return True

    def add_labels(self, issue, labels):
        tmp_labels = issue.fields.labels + labels
        issue.update(fields={"labels": tmp_labels})

    def search_issues(self, query):
        issues = []
        block_size = 100
        idx = 0
        while True:
            issues_tmp = self.jira.search_issues(query, idx, block_size)
            if len(issues_tmp) == 0:
                break
            idx = idx + len(issues_tmp)
            issues = issues + issues_tmp
        return sorted(issues, key=lambda u: str(u.fields.assignee))

    def labeled_issues(self, label, resolution='Done', resolutiondate='-60d'):
        query = f"project = {self.project} AND " \
                f"labels in (\"{label}\") AND " \
                f"resolution = {resolution} AND " \
                f"status != \"Canceled\" AND " \
                f"resolutiondate > {resolutiondate}"
        #f"updatedDate > {updatedDate}"
        return self.search_issues(query)

    def epiced_issues(self, epic_name, createdDate='-60d'):
        query = f"project = {self.project} AND " \
                f"\"Epic Link\" = {epic_name} AND " \
                f"createdDate > {createdDate}"
        return self.search_issues(query)

    def print_issues(self, issues):
        for issue in issues:
            url = self.issue_url_base + issue.key
            labels = issue.fields.labels
            if issue.fields.assignee is not None:
                assignee = issue.fields.assignee.displayName
            else:
                assignee = "None"
            summary = issue.fields.summary
            status = issue.fields.status
            resolution = issue.fields.resolution
            print(f"{url},{assignee},{labels},\"{summary}\",{resolution}")

    def list_unlabeled_issues(self, issues, exclude_label):
        marked_issues = []
        for issue in issues:
            labels = issue.fields.labels
            if len(labels) == 0 or (len(labels) == 1
                                    and labels[0] == exclude_label):
                marked_issues.append(issue)
        return marked_issues