Exemple #1
0
class JiraInstance:
    """
    Class which is response for creataing FixVersion, marking it as RELEASED and for annote tasks which where
    done within lastday.
    """
    def __init__(self, config):
        self.SERVER_ADDRESS = config["SERVER_ADDRESS"]
        self.USER = config["USER"]
        self.PASSWORD = config["PASSWORD"]
        self.PROJECT = config["PROJECT_TAG"]
        self.CHANGELOG_TAG = config["CHANGELOG_TAG"]
        self.tasks = []
        self.fixedVersion = None

        options = {
            'server': self.SERVER_ADDRESS
        }

        self.jira = JIRA(options, basic_auth=(self.USER, self.PASSWORD))

    def is_any_new_changeset_in_changelog(self) -> bool:
        """
        Check if there is any new changeset in changelog in Jira task
        """
        changelog_issue = self.jira.issue(self.CHANGELOG_TAG, fields='comment')
        changelog = self.jira.comments(changelog_issue)[-1]
        regexp = "{}-([1-9]+[0-9]*)".format(self.PROJECT)
        for line in changelog.body.splitlines():
            issueTag = re.search(regexp, line)
            if issueTag is None or issueTag is "":
                continue
            self.tasks.append(issueTag.group())

        return True if len(self.tasks) > 0 else False

    def add_fixed_version_to_tasks(self, fixed_version):
        """
        Create and add fixed version to tasks from chagnelog task pointed in conf.json
        """
        if fixed_version is None or fixed_version is "":
            print("Wrong fixed_version")
            return

        today = datetime.date.today()
        today_str = today.strftime('%Y-%m-%d')
        self.jira.create_version(name=(fixed_version), project=self.PROJECT, released=True, startDate=today_str,
                                 releaseDate=today_str)

        for task in self.tasks:
            print("Add fixed version to {}".format(task))
            issue = self.jira.issue(task, fields='fixVersions')
            issue.add_field_value('fixVersions', {'name': fixed_version})
def main():
    jira = JIRA(**config.JIRA)
    jql = (f'project = {JIRA_PROJECT} AND type = "Backup & Restore" AND '
           f'status NOT IN (Closed, Rejected, Resolved)')

    opened_issues = []
    for issue in jira.search_issues(jql, maxResults=False):
        comments = jira.comments(issue)
        opened_issues.append(Issue(issue, comments))

    if opened_issues:
        subject = f'JIRA ({JIRA_PROJECT}) | Active tasks'
        body = config.SYSINFR_TEMPLATE.render(project=JIRA_PROJECT,
                                              issues=opened_issues,
                                              wiki=config.SETTINGS['wiki'])
        email.notify(subject=subject, message=body)
        logger.info(f'{len(opened_issues)} tasks are found')
    else:
        logger.info('nothing is found')

    jira.close()
def main():
    jira = JIRA(**config.JIRA)
    jql = (
        f'project={JIRA_PROJECT} AND summary ~ JobSummary AND status = Open '
        f'AND created > startOfDay(-{LOOKUP_DAYS}) AND created < now() '
        f'ORDER BY key DESC')

    opened_issues = defaultdict(list)
    for issue in jira.search_issues(jql, maxResults=False):
        services = (config.SETTINGS['sox_services'] +
                    config.SETTINGS['admin_services'])
        for service_name in services:
            if service_name in issue.fields.summary:
                assignee = issue.fields.assignee.name
                email_domain = config.SETTINGS['smtp']['domain']
                email_address = f'{assignee}@{email_domain}'
                issue.fields.summary = service_name
                break
        else:
            logger.error(f'there is unknown service ({issue.fields.summary})')
            continue

        comments = jira.comments(issue)
        opened_issues[email_address].append(Issue(issue, comments))

    for email_address, issues in opened_issues.items():
        subject = f'{issues[0].summary} | Backup monitoring'
        body = config.SOX_TEMPLATE.render(project=JIRA_PROJECT,
                                          issues=issues,
                                          wiki=config.SETTINGS['wiki'])
        recipients = config.SMTP_PARAMS['to'] + [email_address]
        email.notify(subject=subject, to=recipients, message=body)
        logger.info(f'{len(opened_issues)} tasks are found')

    if not opened_issues:
        logger.info('nothing is found')

    jira.close()
Exemple #4
0
status_to_emoji = {
  'Blocked': ':scream:',
  'In Progress': ':sunglasses:',
  'Open': ':doge:',
  'Soft Launch': ':pray:',
  'Rollout': ':chart_with_upwards_trend:',
  'Done': ':tada:'
}


lines = []
issue_changes = []
for i in issues:
    print u'• %s[%s]\t%s' % (i.key.ljust(10), i.fields.status, i.fields.summary)
    lastcomment = jira.comments(i)
    if lastcomment:
        lastcomment = max(lastcomment, key=lambda c: c.updated)
        print '> ', lastcomment.body
        print '-' * 40

    # Ask if wanting to transition the ticket
    transition_selection = ''
    transition_response = raw_input("Would you like to transition this ticket?(y/n) ")
    if transition_response == 'y':
        transitions = jira.transitions(i)
        transition_dict = {}
        for t in transitions:
            transition_dict[t['id']] = t['name']
            print t['id'], '->', t['name']
        transition_options = set(transition_dict.keys())
def main():

    # Parse arguments. Assume any error handling happens in parse_args()
    try:
        args = parse_args()
    except Exception as err:
        print(f"Failed to parse arguments: {err}", file=sys.stderr)

    start_datetime = args["start_datetime"]
    end_datetime = args["end_datetime"]
    detailed = args["detailed"]

    num_issues_done = 0
    num_done_issues_code_reviewed = 0

    # Connect to Jira
    options = {"server": "https://opensciencegrid.atlassian.net"}
    jira = JIRA(options)

    # We need to find the key of the "Development" field, to determine if an issue has commits
    development_field_key = None
    fields = jira.fields()
    name_map = {field['name']: field['id'] for field in fields}
    if "Development" in name_map:
        development_field_key = name_map["Development"]

    # Iterate over all completed issues in the HTCONDOR project
    issues = jira.search_issues(
        "project = HTCONDOR AND type in (Improvement, Bug) AND status = Done",
        expand="changelog",
        maxResults=False)
    for issue in issues:
        issue_changelog = issue.changelog.histories
        issue_isdone = False
        for change in issue_changelog:
            for item in change.items:
                # Issues can be marked Done multiple times, so break after first occurrence
                if issue_isdone:
                    break
                # Check if this change was setting the status to Done
                if item.field == "status" and item.toString == "Done":
                    # Check if this issue has any code commits. If not, don't count it.
                    development_data = getattr(issue.fields,
                                               development_field_key)
                    if development_data == "{}":
                        continue
                    # Strip the time offset from the change.created string
                    changed = change.created[0:change.created.rfind("-")]
                    # Was this issue set to Done status between the provided end and start dates?
                    changed_datetime = datetime.strptime(
                        changed, "%Y-%m-%dT%H:%M:%S.%f")
                    if changed_datetime > start_datetime and changed_datetime < end_datetime:
                        if detailed is True:
                            print(
                                f"{issue.key}: {issue.fields.summary}, Marked Done: {changed_datetime.strftime('%Y-%m-%d %H:%M:%S')}"
                            )
                        num_issues_done += 1
                        issue_isdone = True
                        # Now check the issue comments for a "code review" text entry
                        comments = jira.comments(issue)
                        if len(comments) > 0:
                            for comment in comments:
                                if "code review" in comment.body.lower()[0:20]:
                                    num_done_issues_code_reviewed += 1
                                    if detailed is True:
                                        print("\tThis issue was code reviewed")
                                    break

    print(
        f"\nBetween {start_datetime.strftime('%Y-%m-%d')} and {end_datetime.strftime('%Y-%m-%d')}:\n"
    )
    print(f"{num_issues_done} HTCONDOR issues were marked Done")
    print(
        f"{num_done_issues_code_reviewed} of these completed issues were code reviewed"
    )
    if num_issues_done > 0:
        print(
            f"Code review rate: {round(num_done_issues_code_reviewed*100/num_issues_done, 2)}%\n"
        )
    else:
        print(f"No issues marked Done between the dates specified.")
issues = jira.search_issues('project = 10000 AND assignee!=currentUser()',
                            maxResults=0)

issue = jira.issue('MYP-6')
summary = issue.fields.summary
issue.fields.worklog.worklogs
issue.fields.worklog.worklogs[0].author
issue.fields.worklog.worklogs[0].timeSpent
issue.fields.worklog.worklogs[0].updated

jira.assign_issue(issue, 'Anakin')
transitions = jira.transitions(issue)
transition_serie = [(t['id'], t['name']) for t in transitions]

comments_b = jira.comments(issue)
print(comments_b)
comment = jira.comment('MYP-6', '10300')
print(comment.body)

issue_dict = {
    'project': {
        'id': 10000
    },
    'summary': 'New issue from jira-python',
    'description': 'Look into this one',
    'issuetype': {
        'name': 'Snake'
    },
}
new_issue = jira.create_issue(fields=issue_dict)
Exemple #7
0
class TestIssues(unittest.TestCase):
    def setUp(self):
        self.jira = JIRA(options=dict(server=TEST_URL, verify=False), basic_auth=(TEST_USERNAME, TEST_PASSWORD))
        self.issue1 = self.jira.create_issue(
            project='KB',
            summary='Test-1',
            issuetype={'name': 'Bug'},
        )
        self.issue2 = self.jira.create_issue(
            project='KB',
            summary='Test-2',
            issuetype={'name': 'Bug'},
        )

    def tearDown(self):
        issues = self.jira.search_issues('project = "KB" AND summary ~ "Test*"', fields=['key'])
        for _ in issues:
            _.delete()

    def assert_single_attachment(self):
        # TODO - Find how to test this automatically
        pass

    def assert_single_comment_with(self, text):
        comments = self.jira.comments(self.issue1.key)
        self.assertEqual(len(comments), 1)
        self.assertIn(text, comments[0].body)

    def test_new(self):
        result = CliRunner().invoke(topcli, ['issue', 'new', 'KB', 'task', 'Test-new'])
        self.assertEqual(result.exit_code, 0)
        issues = self.jira.search_issues('project = "KB" AND summary ~ "Test-new"', fields=['key', 'summary'])
        self.assertEqual(len(issues), 1)
        self.assertIn(issues[0].key, result.output)

    def test_transition(self):
        result = CliRunner().invoke(topcli, ['issue', 'transition', self.issue1.key, 'Done'])
        self.assertEqual(result.exit_code, 0)

    def test_assign(self):
        result = CliRunner().invoke(topcli, ['issue', 'assign', self.issue1.key, TEST_USERNAME])
        self.assertEqual(result.exit_code, 0)
        assignee = self.jira.issue(self.issue1.key, fields=['assignee']).fields.assignee
        self.assertEqual(assignee.key, TEST_USERNAME)

    def test_unassign(self):
        result = CliRunner().invoke(topcli, ['issue', 'assign', self.issue1.key, TEST_USERNAME])
        result = CliRunner().invoke(topcli, ['issue', 'unassign', self.issue1.key])
        self.assertEqual(result.exit_code, 0)
        assignee = self.jira.issue(self.issue1.key, fields=['assignee']).fields.assignee
        self.assertIsNone(assignee)

    def test_attach_file(self):
        with CliRunner().isolated_filesystem() as dir_path:
            with open('data.txt', 'w') as f:
                print('abc', file=f)
            result = CliRunner().invoke(topcli, ['issue', 'attach', self.issue1.key, 'data.txt'])
            self.assertEqual(result.exit_code, 0)
            self.assert_single_attachment()

    def test_comment_args(self):
        result = CliRunner().invoke(topcli, ['issue', 'comment', self.issue1.key, 'Comment', 'from args'])
        self.assertEqual(result.exit_code, 0)
        self.assert_single_comment_with('Comment from args')

    def test_comment_file(self):
        with CliRunner().isolated_filesystem() as dir_path:
            with open('comment.txt', 'w') as f:
                print('Comment from file', file=f)
            result = CliRunner().invoke(topcli, ['issue', 'comment', self.issue1.key, 'comment.txt'])
            self.assertEqual(result.exit_code, 0)
            self.assert_single_comment_with('Comment from file')

    def test_comment_prompt(self):
        result = CliRunner().invoke(topcli, ['issue', 'comment', self.issue1.key], input='Comment from prompt\n')
        self.assertEqual(result.exit_code, 0)
        self.assert_single_comment_with('Comment from prompt')

    def test_comment_stdin(self):
        result = CliRunner().invoke(topcli, ['issue', 'comment', self.issue1.key, '-'], input='Comment\nfrom\nstdin')
        self.assertEqual(result.exit_code, 0)
        self.assert_single_comment_with('Comment\nfrom\nstdin')

    def test_link(self):
        result = CliRunner().invoke(topcli, ['issue', 'link', self.issue1.key, self.issue2.key, '-t', 'duplicates'])
        self.assertEqual(result.exit_code, 0)
        links = self.jira.issue(self.issue1.key, fields=['issuelinks']).fields.issuelinks
        self.assertEqual(len(links), 1)
        self.assertEqual(links[0].outwardIssue.key, self.issue2.key)
        self.assertEqual(links[0].type.outward, 'duplicates')

    def test_unlink(self):
        result = CliRunner().invoke(topcli, ['issue', 'link', self.issue1.key, self.issue2.key, '-t', 'duplicates'])
        self.assertEqual(result.exit_code, 0)
        result = CliRunner().invoke(topcli, ['issue', 'unlink', self.issue1.key, self.issue2.key])
        links = self.jira.issue(self.issue1.key, fields=['issuelinks']).fields.issuelinks
        self.assertEqual(len(links), 0)

    def test_search_issue(self):
        result = CliRunner().invoke(topcli, ['issue', 'search'])
        self.assertEqual(result.exit_code, 0)
        self.assertIn('KB-1', result.output)
        self.assertIn('KB-2', result.output)
        self.assertIn('KB-3', result.output)
Exemple #8
0
class JiraCI:
    resolution_state = {"fixed": "1", "wont fixed": "2", "duplicate": "3", "incomplete": "4", "cannot reproduce": "5",
                        "not a bug": "6", "done": "7"}

    def __init__(self, jira_url, login, password):
        if version_info[1] <= 6:
            options = jira_url
        else:
            options = {"server": jira_url}
        self.jira = JIRA(options, basic_auth=(login, password))

    @staticmethod
    def debug_jira(text):
        stdout.write("[DEBUG JIRA]: {0}\n".format(text))

    def check_issue_exist(self, issue_id):
        try:
            self.jira.issue(issue_id)
        except JIRAError as e:
            print "[-] : {0} - {1}".format(issue_id, e.text)
            return False
        else:
            return True

    def check_issue_state(self, issue_id, issue_state):
        jira_issue = self.jira.issue(issue_id)
        if jira_issue.fields.status.name.lower() == issue_state.lower():
            return True
        else:
            return False

    def add_comment(self, issue_id, comment, formatting=False):
        jira_issue = self.jira.issue(issue_id)
        if formatting:
            comment = "{code}" + comment + "{code}"
        if not self.check_comment_exist(issue_id, comment):
            self.jira.add_comment(jira_issue, comment)
            self.debug_jira("Comment (for {0}) : {1} added".format(issue_id, comment.rstrip()))
        else:
            self.debug_jira("Comment (for {0}) : {1} already exist".format(issue_id, comment.rstrip()))

    def assign_issue(self, issue_id, assigned_user):
        jira_issue = self.jira.issue(issue_id)
        jira_issue.update(assignee={"name": assigned_user})

    def add_link(self, issue_id, title, url):
        url_object = {"url": url, "title": title}
        if not self.check_link_exist(issue_id, title, url):
            self.jira.add_remote_link(issue_id, url_object)
            self.debug_jira("Link (for {0}) : {1} added".format(issue_id, url))
        else:
            self.debug_jira("Link (for {0}) : {1} already exist".format(issue_id, url))

    def resolve_issue_to_reporter(self, issue_id):
        reporter = self.get_reporter_issue(issue_id)
        self.jira.transition_issue(issue_id, "5", resolution={"id": self.resolution_state["fixed"]})
        self.assign_issue(issue_id, reporter)

    def get_reporter_issue(self, issue_id):
        jira_issue = self.jira.issue(issue_id)
        return jira_issue.fields.reporter.name

    def check_comment_exist(self, issue_id, new_comment):
        comments = [c.body for c in self.jira.comments(issue_id)]
        if new_comment in comments:
            return True
        return False

    def check_link_exist(self, issue_id, title, url):
        links = [l.raw["object"] for l in self.jira.remote_links(issue_id)]
        for link in links:
            if link["title"] == title and link["url"] == url:
                return True
        return False

    def resolve_from_git(self, issue_id, short_commit_message, title_url, package_url):
        if self.check_issue_exist(issue_id):
            if not self.check_issue_state(issue_id, "resolved"):
                self.resolve_issue_to_reporter(issue_id)
                self.debug_jira("Issue {0} already resolve".format(issue_id))
            else:
                self.debug_jira("Issue {0} resolved".format(issue_id))
            self.add_link(issue_id, title_url, package_url)
            self.add_comment(issue_id, short_commit_message, formatting=True)

    def refer_from_git(self, issue_id, commit_message):
        if self.check_issue_exist(issue_id):
            self.add_comment(issue_id, commit_message, formatting=True)
Exemple #9
0
class JiraTool():
    def __init__(self, server, username, password, maxResults = 500):
        self.server = server
        self.basic_auth = (username, password)
        # issues查询的最大值
        self.maxResults = maxResults

    def login(self):
        self.jira = JIRA(server=self.server, basic_auth=self.basic_auth)
        if self.jira == None:
            print('连接失败')
            sys.exit(-1)

    def get_projects(self):
        """
        获得jira 的所有项目
        :return:
        """
        return [(p.key, p.name, p.id) for p in self.jira.projects()]

    def get_components(self, project):
        """
        获得某项目的所有模块
        :param project:
        :return:
        """
        return [(c.name, c.id) for c in self.jira.project_components(self.jira.project(project))]

    def create_component(self, project, compoment, description, leadUserName=None, assigneeType=None,
                         isAssigneeTypeValid=False):
        """
        # 创建项目模块
        :param project: 模块所属项目
        :param compoment:模块名称
        :param description:模块描述
        :param leadUserName:
        :param assigneeType:
        :param isAssigneeTypeValid:
        :return:
        """
        components = self.jira.project_components(self.jira.project(project))
        if compoment not in [c.name for c in components]:
            self.jira.create_component(compoment, project, description=description, leadUserName=leadUserName,
                                       assigneeType=assigneeType, isAssigneeTypeValid=isAssigneeTypeValid)

    def create_issue(self, project, compoment, summary, description, assignee, issuetype, priority='Medium'):
        """
        创建提交bug
        :param project: 项目
        :param issuetype: 问题类型,Task
        :param summary: 主题
        :param compoment: 模块
        :param description: 描述
        :param assignee: 经办人
        :param priority: 优先级
        :return:
        """
        issue_dict = {
            'project': {'key': project},
            'issuetype': {'id': issuetype},
            'summary': summary,
            'components': [{'name': compoment}],
            'description': description,
            'assignee': {'name': assignee},
            'priority': {'name': priority},
        }
        return self.jira.create_issue(issue_dict)

    def delete_issue(self, issue):
        """
        删除bug
        :param issue:
        :return:
        """
        issue.delete()

    def update_issue_content(self, issue, issue_dict):
        """
        更新bug内容
        :param issue:
        :param issue_dict:
            issue_dict = {
                'project': {'key': project},
                'issuetype': {'id': issuetype},
                'summary': summary,
                'components': [{'name': compoment}],
                'description': description,
                'assignee': {'name': assignee},
                'priority': {'name': priority},
            }
        :return:
        """
        issue.update(fields=issue_dict)
    def update_issue_issuetype(self, issue, issuetype):
        """
        更新bug 状态
        :param issue:
        :param issuetype: 可以为id值如11,可以为值如'恢复开启问题'
        :return:
        """
        transitions = self.jira.transitions(issue)
        # print([(t['id'], t['name']) for t in transitions])
        self.jira.transition_issue(issue, issuetype)

    def search_issues(self, jql):
        """
        查询bug
        :param jql: 查询语句,
                    如"project=项目key AND component = 模块 AND status=closed AND summary ~标题 AND description ~描述"
        :return:
        """
        try:
            # maxResults参数是设置返回数据的最大值,默认是50。
            issues = self.jira.search_issues(jql, maxResults=self.maxResults)
        except Exception as e:
            print(e)
            sys.exit(-1)
        return issues
    def search_issue_content(self, issue, content_type):
        """
        获取bug 的相关信息
        :param issue:
        :param content_type:项目project; 模块名称components; 标题summary; 缺陷类型issuetype; 具体描述内容description;
                            经办人assignee; 报告人reporter; 解决结果resolution; bug状态status; 优先级priority;
                            创建时间created; 更新时间updated; 评论comments
        :return:
        """
        # 评论
        if content_type == 'comments':
            return [c.body for c in self.jira.comments(issue)]
        if hasattr(issue.fields, content_type):
            result = getattr(issue.fields, content_type)
            if isinstance(result, list):
                return [c.name for c in result if hasattr(c, 'name')]
            return result

    def collect_bug_report(self, project, root_path):
        """
        搜集bug报告上模块信息
        :param project:
        :param root_path:
        :return:
        """
        import os

        statistics_path = root_path + '/BugReport/statistics/'
        reports_path = root_path + '/BugReport/reports/' + project + '/'
        if not os.path.exists(statistics_path):
            os.makedirs(statistics_path)
        if not os.path.exists(reports_path):
            os.makedirs(reports_path)

        text = 'id, Key, Type, Status, Resolution, Priority, components, AffectsVersions, FixedVersions, Reporter, ' \
               'Creator, Assignee, CreatedDate, ResolutionDate, UpdatedDate, Summary\n'
        search_str = 'project = ' + project.upper() + ' AND issuetype = Bug AND status in (Resolved, Closed) AND ' \
                                                      'resolution in (Fixed, Resolved) ORDER BY key ASC'

        start = 0
        max_results_each_search = 1000
        while True:
            try:
                # maxResults参数是设置返回数据的最大值,默认是50。
                issues = self.jira.search_issues(search_str, startAt=start, maxResults=max_results_each_search)
            except Exception as e:
                print(e)
                sys.exit(-1)

            for issue in issues:
                text += issue.id + ','
                text += issue.key + ','
                text += issue.fields.issuetype.name + ','
                text += issue.fields.status.name + ','
                text += issue.fields.resolution.name + ','
                priority_name = 'Unassigned' if issue.fields.priority is None else issue.fields.priority.name
                text += priority_name + ','
                components = issue.fields.components
                if len(components) > 0:
                    for component in components:
                        text += component.name + '|'
                text += ','
                if hasattr(issue.fields, 'versions'):
                    for version in issue.fields.versions:
                        text += version.name + '|'
                text += ','
                for version in issue.fields.fixVersions:
                    text += version.name + '|'
                text += ','
                reporterName = 'Unassigned' if issue.fields.reporter is None else issue.fields.reporter.displayName
                creatorName = 'Unassigned' if issue.fields.creator is None else issue.fields.creator.displayName
                assigneeName = 'Unassigned' if issue.fields.assignee is None else issue.fields.assignee.displayName
                text += reporterName + ','
                text += creatorName + ','
                text += assigneeName + ','
                text += issue.fields.created + ','
                text += issue.fields.resolutiondate + ','
                text += issue.fields.updated + ','
                text += issue.fields.summary + ','
                # 输出报告内容
                with open(reports_path + issue.key + '.txt', 'w', encoding='utf-8') as fw:
                    description = 'Summary:\n' + issue.fields.summary + '\nDescription:\n'
                    description += '' if issue.fields.description is None else issue.fields.description
                    fw.write(description)
                    fw.close()
                text += '\n'

            start += max_results_each_search
            print(start)
            if start > issues.total:
                break
        # 输出项目统计信息
        with open(statistics_path + project + '.csv', 'w', encoding='utf-8') as fw:
            fw.write(text)
            fw.close()

        print('The collection for bug reports of Project ' + project + ' has finished!')
Exemple #10
0
class JiradoistSyncher(object):
    def __init__(self):
        while True:
            try:
                self._setup_jira()
                self._setup_todoist()
                break
            except Exception as e:
                print "Setup failed, trying again in 10 s"
                print e
                time.sleep(10)

    def _setup_jira(self):
        with open(os.path.join(PASSWORD_BASE, 'jira_'), 'r') as config:
            access_token, access_token_secret = config.read().strip().split(
                ',')

        with open(os.path.join(PASSWORD_BASE, JIRA_KEYFILE), 'r') as keyf:
            key_cert = keyf.read()

        oauth_dict = {
            'access_token': access_token,
            'access_token_secret': access_token_secret,
            'consumer_key': 'jira-api-tests',
            'key_cert': key_cert
        }
        self.jira = JIRA(oauth=oauth_dict)

    def _setup_todoist(self):
        self.clear_temp()
        # Set up Todoist
        with open(os.path.join(PASSWORD_BASE, 'todoist_'), 'r') as config:
            self.td_api = TodoistAPI(config.read().strip())
        self.safe_sync()

    def get_or_create_label(self, text, color):
        label = next((label for label in self.td_api.labels.all()
                      if label['name'] == text and label['color'] == color),
                     None)

        if not label:
            label = self.td_api.labels.add(text, color=color)

        return label

    def update_labels(self, item, issue):
        status = issue.fields.status.name
        label = self.get_or_create_label(status,
                                         label_colors.get(status,
                                                          colors.GRAY))['id']
        item.update(labels=[label])

    def update_urgency(self, item, issue):
        item.update(priority=priority_mapping[issue.fields.priority.id])

    def update_comments(self, item, issue):
        notes = [
            note['content'] for note in self.td_api.notes.all()
            if note['item_id'] == item['id']
        ]

        for comment in self.jira.comments(issue):
            text = self.text_from_jira_comment(comment)
            if text not in notes:
                note = self.td_api.notes.add(item['id'], text)
                print u"Added note to ticket {}: {}".format(issue.key, text)

    def text_from_jira_comment(self, comment):
        return comment.raw['author']['displayName'] + ": " + comment.body

    def clear_temp(self):
        if os.path.exists(os.path.expanduser('~/.todoist-sync')):
            shutil.rmtree(os.path.expanduser('~/.todoist-sync'))

    def sync(self):
        for jql, target_project in JQL_PROJECT_MAPPING.iteritems():
            print "Synching {} <-- {}".format(target_project, jql)
            try:
                proj_id = next(proj.data['id']
                               for proj in self.td_api.projects.all()
                               if proj.data['name'] == target_project)
            except StopIteration:
                print "Cannot find target project {} in todoist".format(
                    target_project)
                continue

            try:
                issues = search_all_issues(self.jira, jql)
            except JIRAError:
                print "Cannot evaluate JQL expression {}".format(jql)
                continue

            items = [
                item for item in self.td_api.items.all()
                if item.data['project_id'] == proj_id
            ]
            keys = [item.data['content'].split(' ')[0] for item in items]
            jira_keys = [issue.key for issue in issues]

            for issue in issues:
                if issue.key not in keys:
                    item = self.td_api.items.add(u'{} {} {}/browse/{}'.format(
                        issue.key, issue.fields.summary, JIRA_SERVER,
                        issue.key),
                                                 project_id=proj_id)
                    print u"Adding task {} {}".format(issue.key,
                                                      issue.fields.summary)
                    items.append(item)
                    keys.append(issue.key)

                item = items[keys.index(issue.key)]
                self.update_labels(item, issue)
                self.update_urgency(item, issue)
                self.update_comments(item, issue)

            for item in items:
                if item.data['content'].split(' ')[0] not in jira_keys:
                    item.delete()
                    print u"Deleting task {}".format(item.data['content'])
            self.safe_sync()

    def safe_sync(self):
        try:
            self.td_api.commit()
            print "Committed.."
            self.td_api.sync()
            print "...aaand synched!"
        except (todoist.api.SyncError,
                requests.exceptions.ConnectionError) as e:
            print "Couldn't sync, restarting connection to todoist..."
            print e
            self._setup_todoist()
Exemple #11
0
    def create_bug_issue(self,
                         channel,
                         summary,
                         description,
                         component,
                         version,
                         labels,
                         attachments={},
                         user=JIRA_USER,
                         passwd=JIRA_PASS,
                         project=JIRA_PROJECT,
                         DRY_RUN=False):
        """
        Creates a bug issue on Jira
        :param channel: The channel to notify
        :param summary: The title summary
        :param description: Description field
        :param component: Component bug affects
        :param version: Version this bug affects
        :param labels: Labels to attach to the issue
        :param user: User to report bug as
        :param passwd: Password
        :param project: Jira project
        """
        def add_attachments(jira, ticketId, attachments):
            for file in attachments:
                urlretrieve(attachments[file], file)
                jira.add_attachment(ticketId, os.getcwd() + '/' + file, file)
                os.unlink(file)

        if user and passwd and project:
            try:
                jira = JIRA(server='https://issues.voltdb.com/',
                            basic_auth=(user, passwd),
                            options=dict(verify=False))
            except:
                self.logger.exception('Could not connect to Jira')
                return
        else:
            self.logger.error(
                'Did not provide either a Jira user, a Jira password or a Jira project'
            )
            return

        # Check for existing bugs for the same test case, if there are any, suppress filing another
        test_case = summary.split(' ')[0]
        existing = jira.search_issues(
            'summary ~ \'%s\' and labels = automatic and status != Closed' %
            test_case)
        if len(existing) > 0:
            self.logger.info('Found open issue(s) for "' + test_case + '" ' +
                             ' '.join([k.key for k in existing]))

            # Check if new failure is on different job than existing ticket, if so comments
            job = summary.split()[-2]
            existing_ticket = jira.issue(existing[0].id)
            if job not in existing_ticket.fields.summary:
                comments = jira.comments(existing[0].id)
                for comment in comments:
                    # Check for existing comment for same job, if there are any, suppress commenting another
                    if job in comment.body:
                        self.logger.info('Found existing comment(s) for "' +
                                         job + '" on open issue')
                        return

                self.logger.info(
                    'Commenting about separate job failure for %s on open issue'
                    % test_case)
                if not DRY_RUN:
                    jira.add_comment(existing[0].id,
                                     summary + '\n\n' + description)
                    add_attachments(jira, existing[0].id, attachments)
            return

        issue_dict = {
            'project': project,
            'summary': summary,
            'description': description,
            'issuetype': {
                'name': 'Bug'
            },
            'labels': labels
        }

        jira_component = None
        components = jira.project_components(project)
        for c in components:
            if c.name == component:
                jira_component = {'name': c.name, 'id': c.id}
                break
        if jira_component:
            issue_dict['components'] = [jira_component]
        else:
            # Components is still a required field
            issue_dict['components'] = ['Core']

        jira_version = None
        versions = jira.project_versions(project)
        version = 'V' + version
        for v in versions:
            if str(v.name) == version.strip():
                jira_version = {'name': v.name, 'id': v.id}
                break
        if jira_version:
            issue_dict['versions'] = [jira_version]
        else:
            # Versions is still a required field
            issue_dict['versions'] = ['DEPLOY-Integration']

        issue_dict['fixVersions'] = [{'name': 'Backlog'}]
        issue_dict['priority'] = {'name': 'Blocker'}

        self.logger.info("Filing ticket: %s" % summary)
        if not DRY_RUN:
            new_issue = jira.create_issue(fields=issue_dict)
            add_attachments(jira, new_issue.id, attachments)
            #self.logger.info('NEW: Reported issue with summary "' + summary + '"')
            if self.connect_to_slack():
                self.post_message(
                    channel,
                    'Opened issue at https://issues.voltdb.com/browse/' +
                    new_issue.key)
            suite = summary.split('.')[-3]
            # Find all tickets within same test suite and link them
            link_tickets = jira.search_issues(
                'summary ~ \'%s\' and labels = automatic and status != Closed and reporter in (voltdbci)'
                % suite)
            for ticket in link_tickets:
                jira.create_issue_link('Related', new_issue.key, ticket)
        else:
            new_issue = None

        return new_issue
Exemple #12
0
class jira_project_dataframes:
    def __init__(self, server, user, auth, project_key, data_directory_path):

        self.project_key = project_key

        # settings dataframe should probably be abstracted to a setting file
        # since issue data parts rarely change, we're building for "in-code" visibility

        self.data_part_settings = pd.DataFrame({
            'data_part': [
                'issues', 'issue_components', 'issue_labels',
                'issue_stakeholders', 'issue_worklogs', 'issue_comments'
            ],
            'key_name': [
                'issue_key', 'issue_component_key', 'issue_label_key',
                'issue_stakeholder_key', 'issue_worklog_id',
                'issue_comments_id'
            ],
            'file_name': [
                'ISSUES.csv', 'ISSUES_COMPONENTS.csv', 'ISSUES_LABELS.csv',
                'ISSUES_STAKEHOLDERS.csv', 'ISSUES_WORKLOGS.csv',
                'ISSUES_COMMENTS.csv'
            ]
        })

        # placeholder for storing dataframes
        # NOTE: DATAFRAMES CAN NOT REALLY BE STORED WITHIN OTHER DATAFRAMES
        #       THIS IS WHY DATAFRAMES ARE NOT COMBINED WITH data_part_settings
        #       TO ACCESS DATAFRAMES, USE THE data_part_name like this:
        #       comments = jdf.data_part_dataframes['issue_comments']
        self.data_part_dataframes = {}

        self.jira_server = server
        self.jira_user = user
        self.jira_auth = auth

        self.jira = JIRA(options={'server': self.jira_server},
                         basic_auth=(self.jira_user, self.jira_auth))
        self.issues = []

        self.data_directory_path = os.path.abspath(data_directory_path)
        self.make_project_data_directory()

    def string_to_datetime(self, x):
        """
        Returns a datetime for a given string.
        
        keyword arguments:
            x: date string
        """
        return_value = ""

        if x:
            return_value = dtup.parse(x)

        return return_value

    def make_delimit_ready(self, x):
        """
        Jira can contain a bunch of characters that will affect the delimiting
        of dataframes that are saved to .csv files such as carraige returns
        in the issue comments, description, or worklog
        
        """
        return_value = ""

        if x:
            x = str(x)
            x = x.replace(
                u"\u201c",
                '"')  #Windows left smart double quote with simple double quote
            x = x.replace(
                u"\u201d", '"'
            )  #Windows right smart double quote with simple double quote
            x = x.replace(
                u"\u2018",
                "'")  #Windows left smart single quote with simple single quote
            x = x.replace(
                u"\u2019", "'"
            )  #Windows right smart single quote with simple single quote
            x = x.replace(u"\u000C", "_")  #Tab with underscore
            x = x.replace(u"\u000D", "-")  #Carraige Return with dash
            x = x.replace(u"\u000A", "-")  #Line Feed with dash

            return_value = str(x).replace(u"\u201c", '"').replace(
                u"\u201d", '"').replace(u"\u2018", "'").replace(
                    u"\u2019", "'").replace(r"\r\n",
                                            "-").replace(r"\r", "---")

        return return_value

    def ifnull(self, value, null_value=None):
        """
        Returns value or null_value if an error is raise or it is "false"
        
        Note: empty strings are not NULL
        
        keyword arguments:
            value: a value to interrogate for null
            null_value: a value to substitute if the given value is null
        """
        return_value = null_value

        try:
            if type(value) is None:
                return_value = value
            else:
                return_value = null_value

        except:
            return_value = null_value

        return return_value

    def make_project_data_directory(self):
        """
        Creates and/or Returns the data directory for the project that is 
        set in the class self.project_key
        """

        try:
            os.mkdir(self.data_directory_path)
        except:
            "Do Nothing"

        return True

    def get_data_part_element(self, data_part_name, element_name):
        """
        Returns the value of the element of a data part
        
        keyword arguments:
            data_part_name: the name of a data_part
            element_name: the name of the element to return
        """

        data_part = self.data_part_settings.loc[
            self.data_part_settings['data_part'] == data_part_name,
            element_name]

        return data_part.iloc[0]

    def file_to_dataframe(self, file_path):
        """
        Retrieves a file as a dataframe. If no file is found with the given
        file_path, then returns None
        
        keyword arguments:
            file_path: the full path to the csv file to open
        """

        return_df = None

        try:
            return_df = pd.read_csv(file_path)

        except FileNotFoundError:
            ist.msg(
                "file_to_dataframe(): data file was not found: '" + file_path +
                "'", True)
            return_df = None

        return return_df

    def get_file_path_for_data_part(self, data_part_name):
        """
        Retrieves the file name from the data_file_name dictionary for a
        given data part name
        
        keyword arguments:
            data_part_name: the name of an issue data part defined in the data_part_settings dataframe
        """

        return_value = ""

        try:
            file_name = self.project_key + "_" + self.get_data_part_element(
                data_part_name, 'file_name')
            file_path = os.path.abspath(self.data_directory_path + "/" +
                                        file_name)
            return_value = file_path
        except:
            return_value = ""

        #ist.msg("get_file_path_for_data_part(): file_path is '" + return_value + "'",True)
        return return_value

    def get_issues_file_mod_date(self):
        """
        Returns the modified date from the ISSUES.csv file using 
        
        If file is not found, returns an empty string
        """

        file_path = self.get_file_path_for_data_part('issues')

        date_string = ''

        if os.path.isfile(file_path):

            timestamp_modified_on = os.path.getmtime(file_path)
            date_modified_on = dt.fromtimestamp(timestamp_modified_on)
            date_modified_on = date_modified_on + timedelta(days=-1)
            date_string = dt.strftime(date_modified_on, "%Y-%m-%d")

        return date_string

    def retrieve_issues(self, jql, max_results=0):
        """
        Retrives all issues for a jira query language query string up to a 
        specified number of results
        
        keyword arguments:
            jql: a jira query language script to pass to Jira
            max_results (default 0): the maximum results to return; 0 is unlimited
        """
        issues = self.jira.search_issues(jql_str=jql, maxResults=max_results)
        self.issues = issues

        ist.msg(str(len(issues)) + " issues retrieved", True)
        return issues

    def retrieve_latest_issues(self, min_updated_date):
        """
        Retrieves all issues that have been updated since the given minimum updated date
        
        keyword arguments:
            min_updated_date: the beginning date to consider issues to retrieve
        """

        min_updated_date = ist.convert_date_string(min_updated_date, '%F')

        jql = "(project = " + self.project_key + ") and (updatedDate >= " + min_updated_date + " or worklogDate >= " + min_updated_date + ")"
        issues = self.retrieve_issues(jql)
        return issues

    def data_part_to_dataframe(self, issues, data_part_name):
        """
        Converts a set of JIRA.issues to a dataframe based on the data_part_name
        provided.
        
        **NOTE, THESE ARE A HARD CODED SET OF COLUMNS**
        **THERE MAY BE MORE COLUMNS AVAILABLE**
        
        keyword arguments:
            issues: a list of JIRA.issue objects
            data_part_name: the data part to return as a dataframe
        """

        return_dataframe = pd.DataFrame()

        if data_part_name == 'issues':
            for issue in issues:
                i = len(return_dataframe) + 1
                return_dataframe.loc[i, 'issue_key'] = issue.key
                return_dataframe.loc[i, 'summary'] = issue.fields.summary
                return_dataframe.loc[i,
                                     'description'] = self.make_delimit_ready(
                                         issue.fields.description)
                return_dataframe.loc[i,
                                     'priority'] = str(issue.fields.priority)
                return_dataframe.loc[i, 'issue_type'] = str(
                    issue.fields.issuetype)
                return_dataframe.loc[i,
                                     'status'] = str(issue.fields.status.name)
                return_dataframe.loc[i, 'stakeholders'] = ",".join(
                    self.ifnull(issue.fields.customfield_10800, [""]))

                return_dataframe.loc[i,
                                     'create_date'] = self.string_to_datetime(
                                         issue.fields.created)
                return_dataframe.loc[i, 'due_date'] = self.string_to_datetime(
                    issue.fields.duedate)
                return_dataframe.loc[i,
                                     'last_viewed'] = self.string_to_datetime(
                                         issue.fields.lastViewed)
                return_dataframe.loc[
                    i, 'resolution_date'] = self.string_to_datetime(
                        issue.fields.resolutiondate)

                return_dataframe.loc[i, 'resolution'] = str(
                    issue.fields.resolution)

                return_dataframe.loc[
                    i, 'total_seconds_spent'] = issue.fields.aggregatetimespent
                return_dataframe.loc[i,
                                     'assignee'] = str(issue.fields.assignee)
                return_dataframe.loc[i,
                                     'reporter'] = str(issue.fields.reporter)
                return_dataframe.loc[i, 'components'] = ",".join(
                    [str(component) for component in issue.fields.components])
                return_dataframe.loc[i, 'labels'] = ",".join(
                    [str(label) for label in issue.fields.labels])

        elif data_part_name == 'issue_components':
            for issue in issues:
                for component in [
                        str(component) for component in issue.fields.components
                ]:
                    i = len(return_dataframe) + 1

                    return_dataframe.loc[i, 'issue_component_key'] = i
                    return_dataframe.loc[i, 'issue_key'] = issue.key
                    return_dataframe.loc[
                        i, 'component'] = self.make_delimit_ready(component)

        elif data_part_name == 'issue_stakeholders':
            for issue in issues:
                if issue.fields.customfield_10800:
                    for stakeholder in [
                            str(stakeholder)
                            for stakeholder in issue.fields.customfield_10800
                    ]:

                        i = len(return_dataframe) + 1

                        return_dataframe.loc[i, 'issue_stakeholder_key'] = i
                        return_dataframe.loc[i, 'issue_key'] = issue.key
                        return_dataframe.loc[
                            i, 'stakeholder'] = self.make_delimit_ready(
                                stakeholder)

        elif data_part_name == 'issue_labels':
            for issue in issues:
                for label in [str(label) for label in issue.fields.labels]:
                    i = len(return_dataframe) + 1

                    return_dataframe.loc[i, 'issue_label_key'] = i
                    return_dataframe.loc[i, 'issue_key'] = issue.key
                    return_dataframe.loc[i, 'label'] = self.make_delimit_ready(
                        label)

        elif data_part_name == 'issue_worklogs':
            for issue in issues:
                for worklog in self.jira.worklogs(issue):
                    i = len(return_dataframe) + 1

                    return_dataframe.loc[i, 'issue_worklog_id'] = i
                    return_dataframe.loc[i, 'issue_key'] = issue.key
                    return_dataframe.loc[i,
                                         'author'] = worklog.author.displayName
                    return_dataframe.loc[i,
                                         'created'] = self.string_to_datetime(
                                             worklog.created)
                    return_dataframe.loc[i,
                                         'started'] = self.string_to_datetime(
                                             worklog.started)
                    return_dataframe.loc[
                        i, 'seconds_spent'] = worklog.timeSpentSeconds

        elif data_part_name == 'issue_comments':
            for issue in issues:
                for comment in self.jira.comments(issue):
                    i = len(return_dataframe) + 1

                    ## it turns out that a comment sent to the Jira from an email
                    ## that has an error will result in no author being included
                    ## and will show as "Anonymous" in the Jira web application

                    ## this causes a missing author attribute in the result of
                    ## jira.comments(issue)

                    try:
                        return_dataframe.loc[i, 'issue_comments_id'] = i
                        return_dataframe.loc[i, 'issue_key'] = issue.key
                        return_dataframe.loc[
                            i, 'author'] = comment.author.displayName
                        return_dataframe.loc[
                            i, 'created'] = self.string_to_datetime(
                                comment.created)
                        return_dataframe.loc[
                            i, 'body_text'] = self.make_delimit_ready(
                                comment.body)
                    except AttributeError:
                        return_dataframe.loc[i, 'issue_comments_id'] = i
                        return_dataframe.loc[i, 'issue_key'] = issue.key
                        return_dataframe.loc[i, 'author'] = 'Unknown'
                        return_dataframe.loc[
                            i, 'created'] = self.string_to_datetime(
                                comment.created)
                        return_dataframe.loc[
                            i, 'body_text'] = self.make_delimit_ready(
                                comment.body)

        else:
            raise ValueError("The data_part_name given: '" + data_part_name +
                             "' does not have a dataframe definition.")

        ist.msg(
            str(len(return_dataframe)) + " records converted for " +
            data_part_name, True)
        return return_dataframe

    def update_dataframe(self, existing_df, updated_df, key_column):
        """
        Returns an given dataframe updated with values of a second given dataframe
        based on a given key. If no updated_df is given or there are 0 records
        in updated_df, then the existing_df is returned.
        
        keyword arguments:
            existing_df: a dataframe that contains the existing information
            updated_df: a dataframe that contains the updated information
            key_column: the string name of the column that is the key for both
        """
        ### technique used is to concatenate (union) rows of both dataframes,
        ### then deduplicate based on the given key

        return_df = None

        existing = ist.dataframe_has_rows(existing_df)
        updated = ist.dataframe_has_rows(updated_df)

        # if we have an existing and updated then update the existing with updated
        if updated & existing:

            # get all issue keys from the updated_df
            issue_keys = list(set(updated_df['issue_key'].tolist()))

            # Remove any records where the issue was modified.
            existing_df = existing_df[~existing_df['issue_key'].isin(issue_keys
                                                                     )]

            #ist.msg("updated_dataframe(): updating existing dataframe with updated records",True)
            return_df = pd.concat([updated_df, existing_df], sort=False)

            return_df = return_df.drop_duplicates(key_column)
            return_df = return_df.sort_values(key_column)
            return_df = return_df.set_index(key_column)
            return_df = return_df.reset_index()

        # if we only have an updated_df, then return the updated_df
        elif (updated) & (not existing):
            ist.msg(
                "updated_dataframe(): missing existing dataframe; returning updated dataframe",
                True)
            return_df = updated_df

        # if we only have an existing_df then return the existing_df
        elif (not updated) & (existing):
            ist.msg(
                "updated_dataframe(): missing updated dataframe; returning existing dataframe",
                True)
            return_df = existing_df

        # we don't have an updated or existing - something's wrong
        else:
            ist.msg(
                "update_dataframe(): there is neither an existing nor updated dataframe passed",
                True)

        # if an updated_df is None (e.g. there was no file found)
        return return_df

    def refresh_data_part(self, data_part_name, issues):
        """
        Refreshes a data part's information based on the updated_date given
        
        keyword arguments:
            data_part_given: the data part to refresh
            issues: the issues to refresh the data part with
        """

        # get info about the data part
        file_path = self.get_file_path_for_data_part(data_part_name)
        key_name = self.get_data_part_element(data_part_name, 'key_name')

        #convert to dataframes and save
        updated_df = self.data_part_to_dataframe(issues, data_part_name)
        existing_df = self.file_to_dataframe(file_path)
        final_df = self.update_dataframe(existing_df, updated_df, key_name)

        # save final dataframe
        self.data_part_dataframes[data_part_name] = final_df
        final_df.to_csv(file_path, index=False)

        return final_df

    def get_project_data(self, from_date=None):
        """
        Refreshes the project data for any issues that have a modified date on 
        or after the given from_date.
               
        If no updated_date is given, the from_date will be one day before the
        modification date of the save ISSUES.csv file.
        
        If no file exists, all issues for the project will be retrieved and
        saved.
        
        keyword arguments:
            from_date (default None): the minimum issue modified date to retrieve updated project data for
        """

        if not self.project_key:
            raise ValueError(
                "A project_key is required to refresh the project information. Update the project_key attribute and rerun."
            )

        start = dt.now()
        ist.msg("###################################################")
        ist.msg("Begin updating issues...", True)

        #retrieve issues from Jira based on the updatedDate given
        #if no updated_date is given, get update date from ISSUES.csv modification date

        if from_date is None:
            ist.msg("No update_date given, using file modified date instead",
                    True)
            from_date = self.get_issues_file_mod_date()

        #if no file exists (i.e. updated_date = ""), then get all issues for the project
        if not from_date:

            #pull full set of issues for the project
            ist.msg(
                "No issues file found.  Retrieving all issues for the project.",
                True)
            issues = self.retrieve_issues(jql="project=" + self.project_key,
                                          max_results=0)

        else:
            #pull just the latest issues
            ist.msg("Retrieving issues modified on or after: " + from_date)
            issues = self.retrieve_latest_issues(from_date)

        # iterate through all data_parts and update issues
        for data_part in self.data_part_settings['data_part'].to_list():
            ist.msg("Updating data_part " + data_part, True)
            self.refresh_data_part(data_part, issues)

        ist.msg("Issue Information update completed...", True, start)
        ist.msg("###################################################")
        return True

    def issue_to_dataframe_all_fields(self, issue):
        """
        Returns a dataframe with all available fields in a Jira Issue object
        
        !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        !! this method is a way to inspect all of the fields in a jira issues object
        !! for quick reference.
        !!
        !! Please note, the value may need to be parsed as a nested Jira object
        !! For full reference, see the documentation at:
        !! https://jira.readthedocs.io/en/master/
        !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        
        PLEASE NOTE: INSPECTION OF THE RETURNED DATAFRAME FROM A VARIABLE
        EXPLORER MAY RESULT IN A RECURSSION ERRROR DUE TO SOME FIELDS CONTAINING
        MULTIPLE JIRA OBJECTS.
        """

        df = pd.DataFrame({'issue_key': [0]})

        df['issue_key'] = issue.key

        df['aggregateprogress'] = self.ifnull(issue.fields.aggregateprogress)
        df['aggregatetimeestimate'] = self.ifnull(
            issue.fields.aggregatetimeestimate)
        df['aggregatetimeoriginalestimate'] = self.ifnull(
            issue.fields.aggregatetimeoriginalestimate)
        df['aggregatetimespent'] = self.ifnull(issue.fields.aggregatetimespent)
        df['assignee'] = self.ifnull(issue.fields.assignee)
        df['components'] = self.ifnull(issue.fields.components)
        df['created'] = self.ifnull(issue.fields.created)
        df['creator'] = self.ifnull(issue.fields.creator)
        df['customfield_10000'] = self.ifnull(issue.fields.customfield_10000)
        df['customfield_10001'] = self.ifnull(issue.fields.customfield_10001)
        df['customfield_10002'] = self.ifnull(issue.fields.customfield_10002)
        df['customfield_10003'] = self.ifnull(issue.fields.customfield_10003)
        df['customfield_10006'] = self.ifnull(issue.fields.customfield_10006)
        df['customfield_10007'] = self.ifnull(issue.fields.customfield_10007)
        df['customfield_10011'] = self.ifnull(issue.fields.customfield_10011)
        df['customfield_10025'] = self.ifnull(issue.fields.customfield_10025)
        df['customfield_10100'] = self.ifnull(issue.fields.customfield_10100)
        df['customfield_10101'] = self.ifnull(issue.fields.customfield_10101)
        df['customfield_10102'] = self.ifnull(issue.fields.customfield_10102)
        df['customfield_10104'] = self.ifnull(issue.fields.customfield_10104)
        df['customfield_10105'] = self.ifnull(issue.fields.customfield_10105)
        df['customfield_10300'] = self.ifnull(issue.fields.customfield_10300)
        df['customfield_10400'] = self.ifnull(issue.fields.customfield_10400)
        df['customfield_10500'] = self.ifnull(issue.fields.customfield_10500)
        df['customfield_10600'] = self.ifnull(issue.fields.customfield_10600)
        df['customfield_10700'] = self.ifnull(issue.fields.customfield_10700)
        df['customfield_10800'] = self.ifnull(issue.fields.customfield_10800)
        df['customfield_10801'] = self.ifnull(issue.fields.customfield_10801)
        df['customfield_10804'] = self.ifnull(issue.fields.customfield_10804)
        df['customfield_10805'] = self.ifnull(issue.fields.customfield_10805)
        df['customfield_10807'] = self.ifnull(issue.fields.customfield_10807)
        df['customfield_10808'] = self.ifnull(issue.fields.customfield_10808)
        df['customfield_10809'] = self.ifnull(issue.fields.customfield_10809)
        df['customfield_10812'] = self.ifnull(issue.fields.customfield_10812)
        df['customfield_10813'] = self.ifnull(issue.fields.customfield_10813)
        df['customfield_10814'] = self.ifnull(issue.fields.customfield_10814)
        df['customfield_10815'] = self.ifnull(issue.fields.customfield_10815)
        df['customfield_10817'] = self.ifnull(issue.fields.customfield_10817)
        df['customfield_10818'] = self.ifnull(issue.fields.customfield_10818)
        df['customfield_10819'] = self.ifnull(issue.fields.customfield_10819)
        df['customfield_10820'] = self.ifnull(issue.fields.customfield_10820)
        df['customfield_10821'] = self.ifnull(issue.fields.customfield_10821)
        df['customfield_10822'] = self.ifnull(issue.fields.customfield_10822)
        df['customfield_10823'] = self.ifnull(issue.fields.customfield_10823)
        df['customfield_10824'] = self.ifnull(issue.fields.customfield_10824)
        df['customfield_10825'] = self.ifnull(issue.fields.customfield_10825)
        df['customfield_10826'] = self.ifnull(issue.fields.customfield_10826)
        df['customfield_10827'] = self.ifnull(issue.fields.customfield_10827)
        df['customfield_10828'] = self.ifnull(issue.fields.customfield_10828)
        df['description'] = self.ifnull(issue.fields.description)
        df['duedate'] = self.ifnull(issue.fields.duedate)
        df['environment'] = self.ifnull(issue.fields.environment)
        df['fixVersions'] = self.ifnull(issue.fields.fixVersions)
        df['issuelinks'] = self.ifnull(issue.fields.issuelinks)
        df['issuetype'] = self.ifnull(issue.fields.issuetype)
        df['labels'] = self.ifnull(issue.fields.labels)
        df['lastViewed'] = self.ifnull(issue.fields.lastViewed)
        df['priority'] = self.ifnull(issue.fields.priority)
        df['progress'] = self.ifnull(issue.fields.progress)
        df['project'] = self.ifnull(issue.fields.project)
        df['reporter'] = self.ifnull(issue.fields.reporter)
        df['resolution'] = self.ifnull(issue.fields.resolution)
        df['resolutiondate'] = self.ifnull(issue.fields.resolutiondate)
        df['security'] = self.ifnull(issue.fields.security)
        df['status'] = self.ifnull(issue.fields.status)
        df['statuscategorychangedate'] = self.ifnull(
            issue.fields.statuscategorychangedate)
        df['subtasks'] = self.ifnull(issue.fields.subtasks)
        df['summary'] = self.ifnull(issue.fields.summary)
        df['timeestimate'] = self.ifnull(issue.fields.timeestimate)
        df['timeoriginalestimate'] = self.ifnull(
            issue.fields.timeoriginalestimate)
        df['timespent'] = self.ifnull(issue.fields.timespent)
        df['updated'] = self.ifnull(issue.fields.updated)
        df['versions'] = self.ifnull(issue.fields.versions)
        df['votes'] = self.ifnull(issue.fields.votes)
        df['watches'] = self.ifnull(issue.fields.watches)
        df['workratio'] = self.ifnull(issue.fields.workratio)

        return df
Exemple #13
0
class Util(object):
    """

    """
    def __init__(self, **kwargs):
        """

        """

        if 'config' in kwargs:
            self._config = kwargs['config']
        else:
            logging.critical("config was not defined")
            raise Exception("config was not defined")

        if 'username' in kwargs:
            self._username = kwargs['username']
        else:
            logging.critical("username was not defined")
            raise Exception("username was not defined")

        if 'password' in kwargs:
            self._password = kwargs['password']
        else:
            logging.critical("password was not defined")
            raise Exception("password was not defined")

        if 'project' in kwargs:
            self._project = kwargs['project']
        else:
            if 'project' in self._config:
                self._project = self._config['project']
                logging.info(
                    "project was set to '%s' from the configuration file" %
                    self._project)
            else:
                self._project = DEFAULT_PROJECT
                logging.info("project was set to default '%s'" % self._project)

        if 'base_url' in kwargs:
            self._base_url = kwargs['base_url']
        else:
            if 'base_url' in self._config:
                self._base_url = self._config['base_url']
                logging.info(
                    "base_url was set to '%s' from the configuration file" %
                    self._base_url)
            else:
                self._base_url = DEFAULT_BASE_URL
                logging.info("base_url was set to default '%s'" %
                             self._base_url)

        if 'add_missing_watchers' in kwargs:
            self._add_missing_watchers = kwargs['add_missing_watchers']
        else:
            if 'add_missing_watchers' in self._config:
                self._add_missing_watchers = self._config[
                    'add_missing_watchers']
                logging.info(
                    "add_missing_watchers was set to '%s' from the configuration file"
                    % self._add_missing_watchers)
            else:
                self._add_missing_watchers = DEFAULT_ADD_MISSING_WATCHERS
                logging.info("add_missing_watchers was set to default '%s'" %
                             self._add_missing_watchers)

        self._jira = None
        self._jra = None

        self._initialize()

    def setProject(self, project):
        """

        :param project:
        :return:
        """
        self._project = project

    def setAddMissingWatchers(self, add_missing_watchers):
        """

        :param add_missing_watchers:
        :return:
        """
        self._add_missing_watchers = add_missing_watchers

    def _initialize(self):
        """

        :return:
        """
        print("Attempting to connect to JIRA at '%s'" % self._base_url)
        self._jira = JIRA(self._base_url,
                          basic_auth=(self._username, self._password))

        print("Attempting to retrieve info for project '%s'" % self._project)
        self._jra = self._jira.project(self._project)

    def getReport(self):
        """

        :return:
        """
        self.report_misc()
        self.report_components()
        self.report_roles()
        self.report_versions()
        self.report_open_issues()

    def report_misc(self):
        """

        :return:
        """
        print(Fore.BLUE + "Project name '%s'" % self._jra.name)

        print(Fore.BLUE + "Project lead '%s'" % self._jra.lead.displayName)

        print(Style.RESET_ALL)

    def report_components(self):
        """

        :return:
        """
        components = self._jira.project_components(self._jra)

        if len(components) > 0:

            print(Fore.BLUE + "Here are the components")

            print(Style.RESET_ALL)

            for c in components:
                print(c.name)
        else:
            print(Fore.RED + "There are no components")

        print(Style.RESET_ALL)

    def report_roles(self):
        """

        :return:
        """
        roles = self._jira.project_roles(self._jra)

        if len(roles) > 0:

            print(Fore.BLUE + "Here are the roles")

            print(Style.RESET_ALL)

            for r in roles:
                print(r)
        else:
            print(Fore.RED + "There are no roles")

        print(Style.RESET_ALL)

    def report_versions(self):
        """

        :return:
        """
        versions = self._jira.project_versions(self._jra)

        if len(versions) > 0:

            print(Fore.BLUE + "Here are the versions")

            print(Style.RESET_ALL)

            for v in reversed(versions):
                print(v.name)
        else:
            print(Fore.RED + "There are no versions")

        print(Style.RESET_ALL)

    def report_watchers(self, issue):
        """

        :param issue:
        :return:
        """

        watcher = self._jira.watchers(issue)

        print("Issue '%s' has '%d' watcher(s)" %
              (issue.key, watcher.watchCount))

        current_watchers_email = {}

        for watcher in watcher.watchers:
            current_watchers_email[watcher.emailAddress] = True
            print("'%s' - '%s'" % (watcher, watcher.emailAddress))
            # watcher is instance of jira.resources.User:
            # print(watcher.emailAddress)

        for watcher_email in self._config['members_email_lookup']:
            if not watcher_email in current_watchers_email:
                print(Fore.RED +
                      "member '%s' needs to be added as a watcher to '%s'" %
                      (watcher_email, issue.key))
                username = self._config['members_email_lookup'][watcher_email]
                print("Need to add username '%s'" % username)
                print(Style.RESET_ALL)
                if self._add_missing_watchers:
                    self._jira.add_watcher(issue, username)
                    print("Exiting")
                    sys.exit(0)

        print(Style.RESET_ALL)

    def checkWatchers(self):
        """

        :return:
        """

        issues = self._jira.search_issues('project= LO AND status !=  Done',
                                          maxResults=DEFAULT_MAX_RESULTS)

        if len(issues) > 0:

            for issue in issues:
                self.report_watchers(issue)

    def report_open_issues(self):

        issues = self._jira.search_issues('project= LO AND status !=  Done',
                                          maxResults=DEFAULT_MAX_RESULTS)

        if len(issues) > 0:
            print(Fore.BLUE +
                  "Found the following '%d' open issues" % len(issues))

            print(Style.RESET_ALL)

            for issue in issues:
                summary = issue.fields.summary
                id = issue.id
                key = issue.key
                print("id '%s' key '%s' summary : '%s'" % (id, key, summary))
                if DEFAULT_REPORT_WATCHERS:
                    self._report_watchers(issue)

        print(Style.RESET_ALL)

    def getComments(self, key):
        """

        :param key:
        :return:
        """
        logging.info("Attempting to retrieve the issue with key '%s'" % key)

        issues = self._jira.search_issues('key = ' + key)
        if len(issues) > 1:
            raise Exception("Expected only one issue for '%s' but found '%d'" %
                            (key, len(issues)))

        if len(issues) == 1:
            # comments = issues[0].fields.comment.comments
            # comments = issues[0].raw['fields']['comment']['comments']
            comments = self._jira.comments(issues[0])

            if len(comments) > 0:
                print("Found the following '%d' comments" % len(comments))
                comment_ctr = 0
                for comment_id in comments:
                    print("-----------------------------------")
                    comment_ctr += 1
                    comment = self._jira.comment(key, comment_id)
                    author = comment.author.displayName
                    date_created = comment.created
                    body = comment.body
                    print(Fore.BLUE + "%d. author '%s' date '%s'" %
                          (comment_ctr, author, date_created))
                    print(Style.RESET_ALL)
                    print(body)
Exemple #14
0
class jiramenu():
    user = None
    auth = None
    config = None
    debug = False
    r = Rofi()
    issues = []
    rofi_list = []

    def __init__(self, config, debug):
        self.config = config
        self.r.status("starting jiramenu")
        try:
            self.auth = JIRA(config['JIRA']['url'],
                             basic_auth=(config['JIRA']['user'],
                                         config['JIRA']['password']))
        except Exception as error:
            self.r.exit_with_error(str(error))
        self.debug = debug

    def log(self, text):
        if not self.debug:
            return
        print(text)

    def show(self, user):
        self.user = user
        if user:
            self.log(f"show issues for: {self.user}")

        query = self.config['JIRA']['query']
        if user:
            query += f" and assignee = {user}"
        self.log(f"Query: {query}")
        if not self.issues:
            self.issues = self.auth.search_issues(query)

        if not self.rofi_list:
            if user:
                self.rofi_list.append(">>ALL")
            else:
                self.rofi_list.append(">>MINE")
            for issue in self.issues:
                issuetext = ''
                if issue.fields.assignee:
                    issuetext = f'[{issue.fields.assignee.name}]'
                if issue.fields.status.id == str(3):  #id:3 = Work in Progress
                    issuetext += '{WIP}'
                issuetext += f'{issue.key}:{issue.fields.summary}'
                self.rofi_list.append(issuetext)

        # print active query plus number of results on top
        index, key = self.r.select(f'{query}[{len(self.rofi_list)}]',
                                   self.rofi_list,
                                   rofi_args=['-i'],
                                   width=100)
        del key
        if index < 0:
            exit(1)
        if index == 0:
            self.issues = []
            self.rofi_list = []
            if user:
                self.show(None)
            else:
                self.show(self.config['JIRA']['user'])
            return
        self.show_details(index, user)

    def addComment(self, ticket_number):
        comment = self.r.text_entry("Content of the comment:")
        if comment:
            # replace @user with [~user]
            comment = re.sub(r"@(\w+)", r"[~\1]", comment)
            self.auth.add_comment(ticket_number, comment)

    def show_details(self, index, user):
        inputIndex = index
        ticket_number = re.sub(r"\[.*\]", "", self.rofi_list[index])
        ticket_number = re.sub(r"\{.*\}", "", ticket_number)
        ticket_number = ticket_number.split(":")[0]
        self.log("[details]" + ticket_number)
        issue_description = self.issues[index - 1].fields.description

        output = []
        output.append(">>show in browser")
        output.append("[[status]]")
        output.append(self.issues[index - 1].fields.status.name)
        output.append("[[description]]")
        output.append(issue_description)

        if self.auth.comments(ticket_number):
            output.append("[[comments]]")
            comment_ids = self.auth.comments(ticket_number)
            for comment_id in comment_ids:
                self.log("comment_id: " + str(comment_id))
                commenttext = '[' + self.auth.comment(
                    ticket_number, comment_id).author.name + ']'
                commenttext += self.auth.comment(ticket_number,
                                                 comment_id).body
                output.append(commenttext)
        else:
            output.append("[[no comments]]")
        output.append(">>add comment")
        if self.issues[index - 1].fields.assignee:
            output.append("[[assignee]]" +
                          self.issues[index - 1].fields.assignee.name)
        else:
            output.append(">>assign to me")

        if self.issues[index - 1].fields.status.id == str(3):  # WIP
            output.append(">>in review")
        else:
            output.append(">>start progress")

        output.append('<<back')
        index, key = self.r.select(ticket_number, output, width=100)
        if index in [-1, len(output) - 1]:
            self.show(user)
            return

        if index == len(output) - 2:  # move issue to 'In Review'
            self.log("[status]" +
                     self.issues[inputIndex - 1].fields.status.name)
            self.log("[transitions]")
            self.log(self.auth.transitions(ticket_number))
            if self.issues[inputIndex - 1].fields.status.id == str(3):  # WIP
                for trans in self.auth.transitions(ticket_number):
                    if trans['name'] == "in Review":
                        self.log("move to 'in Review'")
                        self.auth.transition_issue(ticket_number, trans['id'])

            else:
                for trans in self.auth.transitions(ticket_number):
                    if trans['name'] == "Start Progress":
                        self.log("move to 'Start Progress'")
                        self.auth.transition_issue(ticket_number, trans['id'])
            self.show_details(inputIndex, user)
            return

        if index == len(output) - 4:  # add comment
            self.log("[addComment]")
            self.addComment(ticket_number)
            self.show_details(inputIndex, user)
            return

        if index == len(output) - 3:  # assign to me
            self.log("[assign to me]")
            self.auth.assign_issue(ticket_number, self.config['JIRA']['user'])
            self.show_details(inputIndex, user)
            return

        if index in [3, 4]:
            Popen(['notify-send', issue_description, '-t', '30000'])
            self.show_details(inputIndex, user)
            return

        # show in browser
        self.log("[show in browser]")
        uri = self.auth.issue(ticket_number).permalink()
        Popen(['nohup', self.config['JIRA']['browser'], uri],
              stdout=DEVNULL,
              stderr=DEVNULL)
Exemple #15
0
class JiraClient(object):
    """
    Helper class for the JIRA
    """

    def __init__(self, url, username, password):
        """
        :param url:
        :param username:
        :param password:
        :return:
        """
        self.url = url
        self.webhook_url = self.url.strip('/') + '/rest/webhooks/1.0/webhook'
        self.basic_auth = (username, password)
        self.client = JIRA(url, basic_auth=self.basic_auth)

    def __str__(self):
        return '{} {}'.format(self.__class__.__name__, self.url)

################
#   Accounts   #
################

    def groups(self):
        """
        :return:
        """
        return self.client.groups()

    def users(self, json_result=True):
        """
        :param json_result:
        :return:
        """
        return self._search_users('_', json_result=json_result)

    def users_by_email(self, email, json_result=True):
        """
        :param email:
        :param json_result:
        :return:
        """
        return self._search_users(email, json_result=json_result)

    def users_by_group(self, name):
        """
        :param name:
        :return:
        """
        try:
            return self.client.group_members(name)
        except JIRAError as exc:
            logger.warning(exc)
            return dict()

    def _search_users(self, qstr, json_result=True):
        """
        :param qstr:
        :param json_result:
        :return:
        """
        def _user_dict_format(user):  # Note: Keep the consistent return format with the users_by_group method
            return {'key':      user.key,
                    'active':   user.active,
                    'fullname': user.displayName,
                    'email':    user.emailAddress
                    }

        users = self.client.search_users(qstr)

        if json_result:  # Note: Keep the consistent return format with the users_by_group method
            return [_user_dict_format(user) for user in users]
        else:
            return users

#############
#  Project  #
#############

    def create_project(self, key):
        """
        :param key:
        :return:
        """
        return self.client.create_project(key)

    def get_project(self, key, json_result=True):
        """
        :param key:
        :param json_result:
        :return:
        """
        if json_result:
            return self.client.project(key).raw
        else:
            return self.client.project(key)

    def get_projects(self, json_result=True):
        """
        :param json_result:
        :return:
        """
        project_objs = self.client.projects()
        if json_result:
            return [_each.raw for _each in project_objs]
        else:
            return project_objs

    def delete_project(self, key):
        """
        :param key:
        :return:
        """
        return self.client.delete_project(key)

#############
#  Version  #
#############

    def get_project_versions(self, name, json_result=True):
        """
        :param name: project name
        :param json_result:
        :return:
        """
        try:
            version_objs = self.client.project_versions(name)
            if json_result:
                return [_each.name for _each in version_objs]
            else:
                return version_objs
        except Exception as exc:
            logger.warn(exc)
            return []

    def create_project_version(self, name, project_name, **kwargs):
        """
        :param name: version name
        :param project_name: project name
        :param kwargs:
        :return:
        """
        return self.client.create_version(name, project_name, **kwargs)

#############
#   fields  #
########### #

    def get_fields(self):
        """
        :return:
        """
        return self.client.fields()

    def get_non_custom_fields(self):
        """
        :return:
        """
        return [each for each in self.client.fields() if not each.get('custom', True)]

    def get_custom_fields(self):
        """
        :return:
        """
        return [each for each in self.client.fields() if each.get('custom', True)]

    def get_field_id_by_name(self, name):
        """
        :param name:
        :return:
        """
        ids = [each['id'] for each in self.client.fields() if each.get('name', '') == name]
        if ids:
            return ids[0]
        else:
            return None

    def get_field_id_for_hours_left(self):
        """
        :return:
        """
        # For Argo customized field
        name = 'Hrs Left'
        return self.get_field_id_by_name(name)

############
#  issues  #
############

    def get_issue(self, name, json_result=True):
        """
        :param name:
        :param json_result:
        :return:
        """
        try:
            issue_obj = self.client.issue(name)
        except JIRAError as exc:
            logger.warn('Not found: %s', exc)
            return None

        if json_result:
            issue_dict = copy.deepcopy(issue_obj.raw['fields'])
            issue_dict['url'] = issue_obj.self
            issue_dict['id'] = issue_obj.id
            issue_dict['key'] = issue_obj.key
            # Replace custom field name
            return issue_dict
        else:
            return issue_obj

    def add_fix_version_to_issue(self, issue_name, version_name, issuetype=None):
        """
        :param issue_name:
        :param version_name:
        :param issuetype:
        :return:
        """
        return self._add_versions_from_issue(issue_name, version_name, issuetype=issuetype, _version_type='fixVersions')

    def add_affected_version_to_issue(self, issue_name, version_name, issuetype=None):
        """
        :param issue_name:
        :param version_name:
        :param issuetype:
        :return:
        """
        return self._add_versions_from_issue(issue_name, version_name, issuetype=issuetype, _version_type='versions')

    def _add_versions_from_issue(self, issue_name, version_name, issuetype=None, _version_type='fixVersions'):
        """
        :param issue_name:
        :param version_name:
        :param issuetype:
        :param _version_type:
        :return:
        """
        assert _version_type in ['fixVersions', 'versions'], 'Unsupported version type'

        issue_obj = self.get_issue(issue_name, json_result=False)

        if not issue_obj:
            return None

        if issuetype is not None and issue_obj.fields.issuetype.name.lower() != issuetype.lower():
            logger.info('SKIP. The issue type is %s, expected issue type is %s',
                        issue_obj.fields.issuetype.name.lower(),
                        issuetype.lower())
            return None

        logger.info('Update issue %s, with %s %s', issue_obj.key, _version_type, version_name)
        ret = issue_obj.add_field_value(_version_type, {'name': version_name})
        issue_obj.update()
        return ret

    def remove_affected_versions_from_issue(self, issue_name, versions_to_remove):
        """
        :param issue_name:
        :param versions_to_remove:
        :return:
        """
        return self._remove_versions_from_issue(issue_name, versions_to_remove, _version_type='versions')

    def remove_fix_versions_from_issue(self, issue_name, versions_to_remove):
        """
        :param issue_name:
        :param versions_to_remove:
        :return:
        """
        return self._remove_versions_from_issue(issue_name, versions_to_remove, _version_type='fixVersions')

    def _remove_versions_from_issue(self, issue_name, versions_to_remove, _version_type='fixVersions'):
        """
        :param issue_name:
        :param versions_to_remove:
        :param _version_type:
        :return:
        """
        assert _version_type in ['fixVersions', 'versions'], 'Unsupported version type'
        if type(versions_to_remove) not in [list, tuple, set]:
            versions_to_remove = [versions_to_remove]
        versions = []
        issue_obj = self.get_issue(issue_name, json_result=False)
        for ver in getattr(issue_obj.fields, _version_type):
            if ver.name not in versions_to_remove:
                versions.append({'name': ver.name})
        issue_obj.update(fields={_version_type: versions})

    def create_issue(self, project, summary, description=None, issuetype='Bug', reporter=None, **kwargs):
        """
        :param project:
        :param summary:
        :param description:
        :param issuetype:
        :param reporter:
        :param kwargs:
        :return:
        """
        # {
        #     "fields": {
        #        "project":
        #        {
        #           "key": "TEST"
        #        },
        #        "summary": "Always do right. This will gratify some people and astonish the REST.",
        #        "description": "Creating an issue while setting custom field values",
        #        "issuetype": {
        #           "name": "Bug"
        #        },
        #        "customfield_11050" : {"Value that we're putting into a Free Text Field."}
        #    }
        # }

        fields_dict = dict()

        for k, v in kwargs.items():
            fields_dict[k] = v

        fields_dict['project'] = {'key': project}
        fields_dict['description'] = description or ''
        fields_dict['summary'] = summary
        fields_dict['issuetype'] = {'name': issuetype}

        if reporter:
            users = self.users_by_email(reporter, json_result=False)
            if users:
                fields_dict['reporter'] = {'name': users[0].name}

        return self.client.create_issue(fields=fields_dict)

    def update_issue(self, name, **kwargs):
        """
        :param name:
        :param kwargs:
        :return:
        """
        issue_obj = self.get_issue(name, json_result=False)
        issue_obj.update(**kwargs)

######################
#   issue comments   #
######################

    def get_issue_comments(self, issue_name, latest_num=5, json_result=True):
        """
        :param issue_name:
        :param latest_num:
        :param json_result:
        :return:
        """
        try:
            comments = self.client.comments(issue_name)
        except JIRAError as exc:
            logger.warn(exc)
            return []
        comments = comments[::-1][:latest_num]
        if json_result:
            return [each.raw for each in comments]
        else:
            return comments

    def add_issue_comment(self, issue_name, msg, commenter=None):
        """
        :param issue_name:
        :param msg:
        :param commenter:
        :return:
        """
        if commenter:
            users = self.users_by_email(commenter, json_result=False)
            if users:
                msg_header = 'The comment is created by {}({}) from AX system. \n\n'.\
                    format(users[0].displayName, users[0].emailAddress)
                msg = msg_header + msg
        return self.client.add_comment(issue_name, msg)

###############
#  issue type #
###############

    def get_issue_types(self, json_result=True):
        """
        :param json_result:
        :return:
        """
        objs = self.client.issue_types()
        if json_result:
            return [obj.raw for obj in objs]
        else:
            return objs

    def get_issue_type_by_name(self, name, json_result=True):
        """
        :param name:
        :param json_result:
        :return:
        """
        try:
            obj = self.client.issue_type_by_name(name)
        except KeyError as exc:
            logger.warn(exc)
            return None
        else:
            if json_result:
                return obj.raw
            else:
                return obj

#############
#   Query   #
#############

    def query_issues(self, **kwargs):
        """
        :param kwargs:
        :return:

        max_results: maximum number of issues to return. Total number of results
                     If max_results evaluates as False, it will try to get all issues in batches.
        json_result: JSON response will be returned when this parameter is set to True.
                     Otherwise, ResultList will be returned
        """
        SUPPORTED_KEYS = ('project', 'status', 'component', 'labels', 'issuetype', 'priority',
                          'creator', 'assignee', 'reporter', 'fixversion', 'affectedversion')
        max_results = kwargs.pop('max_results', 100)
        _json_result = kwargs.pop('json_result', False)
        jql_str_list = []
        for k, v in kwargs.items():
            if k not in SUPPORTED_KEYS:
                continue
            jql_str_list.append('{} = "{}"'.format(k.strip(), v.strip()))
        if jql_str_list:
            jql_str = ' AND '.join(jql_str_list)
        else:
            jql_str = ''  # Fetch ALL issues
        try:
            ret = self.client.search_issues(jql_str, maxResults=max_results, json_result=_json_result)
        except Exception as exc:
            logger.warn(exc)
            ret = {"issues": []}
        return ret

################
# Query Issues #
################

    def get_issues_by_project(self, project_name, **kwargs):
        """
        :param project_name:
        :param kwargs:
        :return:
        """
        return self.query_issues(project=project_name, **kwargs)

    def get_issues_by_component(self, component, **kwargs):
        """
        :param component:
        :param kwargs:
        :return:
        """
        return self.query_issues(component=component, **kwargs)

    def get_issues_by_assignee(self, assignee, **kwargs):
        """
        :param assignee:
        :param kwargs:
        :return:
        """
        return self.query_issues(assignee=assignee, **kwargs)

    def get_issues_by_status(self, status, **kwargs):
        """
        :param status:
        :param kwargs:
        :return:
        """
        return self.query_issues(status=status, **kwargs)

    def get_issues_by_label(self, labels, **kwargs):
        """
        :param labels:
        :param kwargs:
        :return:
        """
        return self.query_issues(labels=labels, **kwargs)

    def get_issues_by_fixversion(self, fix_version, **kwargs):
        """
        :param fix_version:
        :param kwargs:
        :return:
        """
        return self.query_issues(fixverion=fix_version, **kwargs)

    def get_issues_by_affectedversion(self, affected_version, **kwargs):
        """
        :param affected_version:
        :param kwargs:
        :return:
        """
        return self.query_issues(affectedversion=affected_version, **kwargs)

################
#  Query Hours #
################

    def get_total_hours(self, **kwargs):
        """
        :param kwargs:
        :return:
        """
        all_issues = self.query_issues(**kwargs)
        field_id = self.get_field_id_for_hours_left()
        hours = [getattr(iss_obj.fields, field_id) for iss_obj in all_issues]
        return sum([float(each) for each in hours if each])

    def get_total_hours_by_project(self, project_name):
        """
        :param project_name:
        :return:
        """
        return self.get_total_hours(project=project_name)

    def get_total_hours_by_component(self, component):
        """
        :param component:
        :return:
        """
        return self.get_total_hours(component=component)

    def get_total_hours_by_assignee(self, assignee):
        """
        :param assignee:
        :return:
        """
        return self.get_total_hours(assignee=assignee)

    def get_total_hours_by_label(self, label):
        """
        :param label:
        :return:
        """
        return self.get_total_hours(labels=label)

##############
#   webhook  #
##############

    def create_ax_webhook(self, url, projects=None):
        """Create AX Jira webhook
        :param url:
        :param projects:
        :return:
        """
        payload = copy.deepcopy(AX_JIRA_WEBHOOK_PAYLOAD)
        payload['name'] = payload['name'] + self._get_cluster_name()
        payload['url'] = url
        filter_dict = self._generate_project_filter(projects)
        logger.info('Webhook project filter is: %s', filter_dict)
        payload.update(filter_dict)
        return self._requests(self.webhook_url, 'post', data=payload)

    def get_ax_webhook(self):
        """Get AX Jira webhook
        :return:
        """
        response = self._requests(self.webhook_url, 'get')
        wh_name = AX_JIRA_WEBHOOK_PAYLOAD['name'] + self._get_cluster_name()
        ax_whs = [wh for wh in response.json() if wh['name'] == wh_name]
        if not ax_whs:
            logger.error('Could not get Jira webhook for this cluster: %s, ignore it', wh_name)
        else:
            return ax_whs[0]

    def update_ax_webhook(self, projects=None):
        """Update AX Jira webhook
        :param projects:
        :return:
        """
        ax_wh = self.get_ax_webhook()
        if ax_wh:
            filter_dict = self._generate_project_filter(projects)
            logger.info('Webhook project filter is: %s', filter_dict)
            logger.info('Update the webhook %s', ax_wh['self'])
            return self._requests(ax_wh['self'], 'put', data=filter_dict)
        else:
            logger.warn('Skip the webhook update')

    def delete_ax_webhook(self):
        """Delete AX Jira webhook
        :return: 
        """
        response = self._requests(self.webhook_url, 'get')
        wh_name = AX_JIRA_WEBHOOK_PAYLOAD['name'] + self._get_cluster_name()

        ax_whs = [wh for wh in response.json() if wh['name'] == wh_name]
        for wh in ax_whs:
            logger.info('Delete webhook %s', wh['self'])
            self._delete_webhook(url=wh['self'])

    def get_ax_webhooks(self):
         """Get all AX webhooks
         :return:
         """
         response = self._requests(self.webhook_url, 'get')
         webhooks = response.json()
         # filter out non-ax webhooks
         return [wh for wh in webhooks if wh['name'].startswith(AX_JIRA_WEBHOOK_PAYLOAD['name'])]

    def delete_ax_webhooks(self):
        """Delete all AX Jira webhooks
        :return:
        """
        ax_whs = self.get_ax_webhooks()
        for wh in ax_whs:
            logger.info('Delete webhook %s', wh['self'])
            self._delete_webhook(url=wh['self'])

    def _generate_project_filter(self, projects):
        """
        :param projects:
        :return:
        """
        if not projects:
            filter_str = ''
        else:
            project_filter_list = []
            project_objs = self.get_projects(json_result=False)
            for pkey in projects:
                ps = [p for p in project_objs if p.key == pkey]
                if not ps:
                    logger.error('Could not get project %s, ignore it', pkey)
                else:
                    project_filter_list.append('Project = {}'.format(ps[0].name))
            filter_str = ' AND '.join(project_filter_list)

        return {'filters':
                    {'issue-related-events-section': filter_str
                     }
                }

    def _delete_webhook(self, url=None, id=None):
        """Delete webhook
        :param url:
        :param id:
        :return:
        """
        if url is None:
            url = self.webhook_url + '/' + str(id)
        return self._requests(url, 'delete')

    def _get_cluster_name(self):
        """
        :return:
        """
        return os.environ.get('AX_CLUSTER', UNKNOWN_CLUSTER)

    def _requests(self, url, method, data=None, headers=None, auth=None, raise_exception=True, timeout=30):
        """
        :param url:
        :param method:
        :param data:
        :param headers:
        :param auth:
        :param raise_exception:
        :param timeout:
        :return:
        """
        headers = {'Content-Type': 'application/json'} if headers is None else headers
        auth = self.basic_auth if auth is None else auth
        try:
            response = requests.request(method, url, data=json.dumps(data), headers=headers, auth=auth, timeout=timeout)
        except requests.exceptions.RequestException as exc:
            logger.error('Unexpected exception occurred during request: %s', exc)
            raise
        logger.debug('Response status: %s (%s %s)', response.status_code, response.request.method, response.url)
        # Raise exception if status code indicates a failure
        if response.status_code >= 400:
            logger.error('Request failed (status: %s, reason: %s)', response.status_code, response.text)
        if raise_exception:
            response.raise_for_status()
        return response
Exemple #16
0
class TestIssues(unittest.TestCase):
    def setUp(self):
        self.jira = JIRA(options=dict(server=TEST_URL, verify=False),
                         basic_auth=(TEST_USERNAME, TEST_PASSWORD))
        self.issue1 = self.jira.create_issue(
            project='KB',
            summary='Test-1',
            issuetype={'name': 'Bug'},
        )
        self.issue2 = self.jira.create_issue(
            project='KB',
            summary='Test-2',
            issuetype={'name': 'Bug'},
        )

    def tearDown(self):
        issues = self.jira.search_issues(
            'project = "KB" AND summary ~ "Test*"', fields=['key'])
        for _ in issues:
            _.delete()

    def assert_single_attachment(self):
        # TODO - Find how to test this automatically
        pass

    def assert_single_comment_with(self, text):
        comments = self.jira.comments(self.issue1.key)
        self.assertEqual(len(comments), 1)
        self.assertIn(text, comments[0].body)

    def test_new(self):
        result = CliRunner().invoke(topcli,
                                    ['issue', 'new', 'KB', 'task', 'Test-new'])
        self.assertEqual(result.exit_code, 0)
        issues = self.jira.search_issues(
            'project = "KB" AND summary ~ "Test-new"',
            fields=['key', 'summary'])
        self.assertEqual(len(issues), 1)
        self.assertIn(issues[0].key, result.output)

    def test_transition(self):
        result = CliRunner().invoke(
            topcli, ['issue', 'transition', self.issue1.key, 'Done'])
        self.assertEqual(result.exit_code, 0)

    def test_assign(self):
        result = CliRunner().invoke(
            topcli, ['issue', 'assign', self.issue1.key, TEST_USERNAME])
        self.assertEqual(result.exit_code, 0)
        assignee = self.jira.issue(self.issue1.key,
                                   fields=['assignee']).fields.assignee
        self.assertEqual(assignee.key, TEST_USERNAME)

    def test_unassign(self):
        result = CliRunner().invoke(
            topcli, ['issue', 'assign', self.issue1.key, TEST_USERNAME])
        result = CliRunner().invoke(topcli,
                                    ['issue', 'unassign', self.issue1.key])
        self.assertEqual(result.exit_code, 0)
        assignee = self.jira.issue(self.issue1.key,
                                   fields=['assignee']).fields.assignee
        self.assertIsNone(assignee)

    def test_attach_file(self):
        with CliRunner().isolated_filesystem() as dir_path:
            with open('data.txt', 'w') as f:
                print('abc', file=f)
            result = CliRunner().invoke(
                topcli, ['issue', 'attach', self.issue1.key, 'data.txt'])
            self.assertEqual(result.exit_code, 0)
            self.assert_single_attachment()

    def test_comment_args(self):
        result = CliRunner().invoke(
            topcli,
            ['issue', 'comment', self.issue1.key, 'Comment', 'from args'])
        self.assertEqual(result.exit_code, 0)
        self.assert_single_comment_with('Comment from args')

    def test_comment_file(self):
        with CliRunner().isolated_filesystem() as dir_path:
            with open('comment.txt', 'w') as f:
                print('Comment from file', file=f)
            result = CliRunner().invoke(
                topcli, ['issue', 'comment', self.issue1.key, 'comment.txt'])
            self.assertEqual(result.exit_code, 0)
            self.assert_single_comment_with('Comment from file')

    def test_comment_prompt(self):
        result = CliRunner().invoke(topcli,
                                    ['issue', 'comment', self.issue1.key],
                                    input='Comment from prompt\n')
        self.assertEqual(result.exit_code, 0)
        self.assert_single_comment_with('Comment from prompt')

    def test_comment_stdin(self):
        result = CliRunner().invoke(topcli,
                                    ['issue', 'comment', self.issue1.key, '-'],
                                    input='Comment\nfrom\nstdin')
        self.assertEqual(result.exit_code, 0)
        self.assert_single_comment_with('Comment\nfrom\nstdin')

    def test_link(self):
        result = CliRunner().invoke(topcli, [
            'issue', 'link', self.issue1.key, self.issue2.key, '-t',
            'duplicates'
        ])
        self.assertEqual(result.exit_code, 0)
        links = self.jira.issue(self.issue1.key,
                                fields=['issuelinks']).fields.issuelinks
        self.assertEqual(len(links), 1)
        self.assertEqual(links[0].outwardIssue.key, self.issue2.key)
        self.assertEqual(links[0].type.outward, 'duplicates')

    def test_unlink(self):
        result = CliRunner().invoke(topcli, [
            'issue', 'link', self.issue1.key, self.issue2.key, '-t',
            'duplicates'
        ])
        self.assertEqual(result.exit_code, 0)
        result = CliRunner().invoke(
            topcli, ['issue', 'unlink', self.issue1.key, self.issue2.key])
        links = self.jira.issue(self.issue1.key,
                                fields=['issuelinks']).fields.issuelinks
        self.assertEqual(len(links), 0)

    def test_search_issue(self):
        result = CliRunner().invoke(topcli, ['issue', 'search'])
        self.assertEqual(result.exit_code, 0)
        self.assertIn('KB-1', result.output)
        self.assertIn('KB-2', result.output)
        self.assertIn('KB-3', result.output)
Exemple #17
0
class Jira():
    def __init__(self):
        options = {
            'verify': False,
            'server': 'https://jira.test.com'
        }  # Options to connect to JIRA
        WORK_DIR = os.path.dirname(os.path.realpath(__file__))
        CREDENTIAL_FILE = 'credentials.json'
        with open(WORK_DIR + '/' + CREDENTIAL_FILE, 'r') as file:
            credential = json.load(file)
        self.jira = JIRA(options,
                         basic_auth=(credential["jira"]["username"],
                                     credential["jira"]['password']))
        self.user_name = credential["jira"]["username"]
        self.user_pass = credential["jira"]['password']
        self.jira_filter = self.jira.search_issues(
            'project="TEAMNAME: Platform Operations" and reporter=nagios and resolution=Unresolved',
            maxResults=1000)
        self.jira_filter_for_not_updated = self.jira.search_issues(
            'project = TEAMNAME AND resolution = Unresolved AND updated <= -1w AND assignee in (nagios)',
            maxResults=1000)

    def assign_and_investigate(self, issue):
        url = 'https://jira.test.com/rest/api/2/issue/%s/transitions' % issue.key
        auth = base64.encodestring(
            '%s:%s' % (self.user_name, self.user_pass)).replace('\n', '')

        data = json.dumps({'transition': {'id': 11}})

        request = urllib2.Request(
            url, data, {
                'Authorization': 'Basic %s' % auth,
                'Content-Type': 'application/json',
            })
        print urllib2.urlopen(request).read()

    def resolve_ticket(self, issue):
        url = 'https://jira.test.com/rest/api/2/issue/%s/transitions' % issue.key
        auth = base64.encodestring(
            '%s:%s' % (self.user_name, self.user_pass)).replace('\n', '')
        issue.fields.labels.append(u'auto_closed')
        issue.update(fields={"labels": issue.fields.labels})

        data = json.dumps({
            'transition': {
                'id': 71
            },
            'fields': {
                'resolution': {
                    "name": "Self Corrected"
                }
            }
        })
        request = urllib2.Request(
            url, data, {
                'Authorization': 'Basic %s' % auth,
                'Content-Type': 'application/json',
            })
        print urllib2.urlopen(request).read()

    def get_status_by_comment(self, issue):
        if len(self.jira.comments(issue)) > 0:
            last_comment = ""
            for comment in self.jira.comments(issue):

                if str(comment.author) == 'Monitoring':
                    last_comment = comment.body
                else:
                    return False
            status = last_comment.split("*")[6].split()[0]
            if status == "RECOVERY":
                return True
            else:
                return False
        else:
            return False
class JiraTool(object):
    def __init__(self, server, username, password, maxResults=50):
        self.server = server
        self.basic_auth = (username, password)
        # issues查询的最大值
        self.maxResults = maxResults

    def login(self):
        self.jira = JIRA(server=self.server, basic_auth=self.basic_auth)
        if self.jira == None:
            print('连接失败')
            sys.exit(-1)

    def get_projects(self):
        """
        获得jira 的所有项目
        :return:
        """
        return [(p.key, p.name, p.id) for p in self.jira.projects()]

    # def get_components(self, project):
    #     """
    #     获得某项目的所有模块
    #     :param project:
    #     :return:
    #     """
    #     return [(c.name, c.id) for c in self.jira.project_components(self.jira.project(project))]

    def create_component(self,
                         project,
                         compoment,
                         description,
                         leadUserName=None,
                         assigneeType=None,
                         isAssigneeTypeValid=False):
        """
        # 创建项目模块
        :param project: 模块所属项目
        :param compoment:模块名称
        :param description:模块描述
        :param leadUserName:
        :param assigneeType:
        :param isAssigneeTypeValid:
        :return:
        """
        components = self.jira.project_components(self.jira.project(project))
        if compoment not in [c.name for c in components]:
            self.jira.create_component(compoment,
                                       project,
                                       description=description,
                                       leadUserName=leadUserName,
                                       assigneeType=assigneeType,
                                       isAssigneeTypeValid=isAssigneeTypeValid)

    def create_issue(self,
                     project,
                     compoment,
                     summary,
                     description,
                     assignee,
                     issuetype,
                     priority='Medium'):
        """
        创建提交issue
        :param project: 项目
        :param issuetype: 问题类型,Task
        :param summary: 主题
        :param compoment: 模块
        :param description: 描述
        :param assignee: 经办人
        :param priority: 优先级
        :return:
        """
        issue_dict = {
            'project': {
                'key': project
            },
            'issuetype': {
                'id': issuetype
            },
            'summary': summary,
            'components': [{
                'name': compoment
            }],
            'description': description,
            'assignee': {
                'name': assignee
            },
            'priority': {
                'name': priority
            },
        }
        return self.jira.create_issue(issue_dict)

    def delete_issue(self, issue):
        """
        删除issue
        :param issue:
        :return:
        """
        issue.delete()

    def update_issue_content(self, issue, issue_dict):
        """
        更新issue内容
        :param issue:
        :param issue_dict:
            issue_dict = {
                'project': {'key': project},
                'issuetype': {'id': issuetype},
                'summary': summary,
                'components': [{'name': compoment}],
                'description': description,
                'assignee': {'name': assignee},
                'priority': {'name': priority},
            }
        :return:
        """
        issue.update(fields=issue_dict)

    def update_issue_issuetype(self, issue, issuetype):
        """
        更新bug 状态
        :param issue:
        :param issuetype: 可以为id值如11,可以为值如'恢复开启问题'
        :return:
        """
        transitions = self.jira.transitions(issue)
        # print([(t['id'], t['name']) for t in transitions])
        self.jira.transition_issue(issue, issuetype)

    def search_all_issue(self, jql):
        block_size = 100
        block_num = 0
        issues = []
        while True:
            start_idx = block_num * block_size
            part_issues = self.jira.search_issues(jql, start_idx, block_size)
            if len(part_issues) == 0:
                break
            block_num += 1
            issues.extend(part_issues)
        return issues

    # def search_issues(self, jql):
    #     """
    #     查询bug
    #     :param jql: 查询语句,如"project=项目key AND component = 模块 AND status=closed AND summary ~标题 AND description ~描述"
    #     :return:
    #     """
    #     try:
    #         # maxResults参数是设置返回数据的最大值,默认是50。
    #         issues = self.jira.search_issues(jql, maxResults=self.maxResults)
    #     except Exception as e:
    #         print(e)
    #         sys.exit(-1)
    #     return issues

    def search_issue_content(self, issue, content_type):
        """
        获取issue 的相关信息
        :param issue:
        :param content_type:项目project; 模块名称components; 标题summary; 缺陷类型issuetype; 具体描述内容description; 经办人assignee; 报告人reporter; 解决结果resolution; bug状态status; 优先级priority; 创建时间created; 更新时间updated; 评论comments
        :return:
        """
        # 评论
        if content_type == 'comments':
            return [c.body for c in self.jira.comments(issue)]
        if hasattr(issue.fields, content_type):
            result = getattr(issue.fields, content_type)
            if isinstance(result, list):
                return [c.name for c in result if hasattr(c, 'name')]
            return result

    def get_issue_types(self):
        """
        获取所有issues类型
        :return:
        """
        issue_type_name = []
        issue_types = self.jira.issue_types()
        for issue_type in issue_types:
            issue_type_name.append(issue_type.name)
        return issue_type_name

    def get_components(self, issue):
        """
        获取组件字段
        :param issue: 每个issue
        :return:
        """
        for i in issue.fields.components:
            components = i.name
            return components

    def get_epic_link(self, issue):
        """
        获取epic_link字段
        :param issue: 每个issue
        :return:
        """
        for i in issue.fields.customfield_10900:
            epic_link = i.name
            return epic_link

    def make_cse_env_num(self, cse_num):
        try:
            res = cse_num.split('-')
            num = int(res[1]) + 1
            cse_env_nu = 'CSE-' + str(num)
        except Exception as e:
            cse_env_nu = ''
        return cse_env_nu

    def get_result(self, issue):
        """
        获取处理结果的字段  如 产品问题、环境问题
        :param issue:
        :return:
        """
        if issue.fields.customfield_11100:
            result = issue.fields.customfield_11100.value
        else:
            result = ''
        return result

    def get_resolve_time(self, issue):
        try:
            resolve_time = issue.fields.resolutiondate[0:19]
        except Exception as e:
            print(e)
            resolve_time = ''
        print(resolve_time)
        return resolve_time
Exemple #19
0
class JiraNavigate:
    """
    Wraps the JIRA module to easily obtain information on a project. The underlying
    communication requires login details and a REST interface.
    """

    def __init__(self):
        self.jira = JIRA(basic_auth=(session["username"], base64.b64decode(session["password"])),
                         options={'server': session["server"]})

    def get_issues(self, jql):
        logging.debug("JQL: %s" % jql)
        return self.jira.search_issues(jql)

    def get_changelog(self, issue):
        issue = self.jira.issue(self.get_key(issue), expand='changelog')
        return issue.changelog

    def get_resolution_date(self, issue):
        resolution_date = issue.fields.resolutiondate
        if resolution_date is not None:
            return self.to_datetime(resolution_date)
        return None

    def get_comments(self, issue):
        return self.jira.comments(issue)

    @staticmethod
    def get_title(issue):
        return "[%s] %s" % (issue.key, issue.fields.summary)

    @staticmethod
    def get_assignee(issue):
        if issue.fields.assignee is None:
            return ""
        else:
            return issue.fields.assignee.displayName

    @staticmethod
    def get_key(issue):
        return issue.key

    @staticmethod
    def get_summary(issue):
        return issue.fields.summary

    @staticmethod
    def get_components(issue):
        return issue.fields.components

    def get_created_date(self, issue):
        return self.to_datetime(issue.fields.created)

    def get_updated_date(self, issue):
        return self.to_datetime(issue.fields.updated)

    @staticmethod
    def get_labels(issue):
        return issue.fields.labels

    @staticmethod
    def get_story_points(issue):
        story_points = issue.fields.customfield_10103
        if story_points > 0:
            return story_points
        return 0

    def rerank(self, sorted_backlog):
        for pair in zip(sorted_backlog, sorted_backlog[1:]):
            key1 = self.get_key(pair[0]["issue"])
            key2 = self.get_key(pair[1]["issue"])
            self.jira.rank(key2, key1)

    def count_story_points(self, issues):
        points = 0
        for issue in issues:
            points += self.get_story_points(issue)
        return points

    def sprint_dates(self, sprint_id):
        def to_date(date):
            months = ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"]
            p = re.match(r"(\d+)/(\w+)/(\d+)", date)
            year = int(p.group(3))+2000
            month = months.index(p.group(2).lower())+1
            day = int(p.group(1))
            logging.debug("Deconstructed date for {}: {:4d}-{:2d}-{:2d}".format(date, year, month, day))
            return datetime.datetime(year, month, day)

        sprint = self.jira.sprint(sprint_id)
        dates = dict(
            start=to_date(sprint.startDate),
            end=to_date(sprint.endDate)
        )
        logging.debug("{} to {}".format(dates["start"], dates["end"]))
        return dates

    def sprint_name(self, sprint_id):
        return self.jira.sprint(sprint_id)

    def get_sprint_committed_issues(self, sprint_id, board_name):
        jql = "Sprint = {:d} AND issueFunction not in " \
              "addedAfterSprintStart(\"{}\", \"{}\")".format(sprint_id,
                                                             board_name,
                                                             self.sprint_name(sprint_id))
        logging.debug("JQL sprint_committed_points: {}".format(jql))
        return self.jira.search_issues(jql)

    def sprint_committed_points(self, sprint_id, board_name):
        committed_issues = self.get_sprint_committed_issues(sprint_id, board_name)
        return self.count_story_points(committed_issues)

    def sprint_completed_points(self, sprint_id, board_name):
        jql = "issueFunction in completeInSprint(\"{}\", \"{}\")".format(board_name, self.sprint_name(sprint_id))
        logging.debug("JQL sprint_completed_points: {}".format(jql))
        completed_issues = self.jira.search_issues(jql)
        return self.count_story_points(completed_issues)

    def sprint_interrupt_points(self, sprint_id, board_name):
        jql = "(issueFunction in addedAfterSprintStart(\"{}\", \"{}\"))" \
              "AND resolution = Done ".format(board_name,
                                                    self.jira.sprint(sprint_id))
        logging.debug("JQL sprint_interrupt_points: {}".format(jql))
        interrupt_issues = self.jira.search_issues(jql)
        return self.count_story_points(interrupt_issues)

    def sprint_incomplete_points(self, sprint_id, board_name):
        jql = "(Sprint = {:d} AND issueFunction not in addedAfterSprintStart(\"{}\", \"{}\")) " \
              "AND resolution = Unresolved ".format(sprint_id,
                                                    board_name,
                                                    self.sprint_name(sprint_id))
        logging.debug("JQL sprint_incomplete_points: %s" % jql)
        incomplete_issues = self.jira.search_issues(jql)
        return self.count_story_points(incomplete_issues)

    def committed_points_timeline(self, sprint_id, board_name):
        self.sprint_dates(sprint_id)
        committed_issues = self.get_sprint_committed_issues(sprint_id, board_name)

        resolved_on_date = {}
        for issue in committed_issues:
            resolution_date = self.get_resolution_date(issue)
            if resolution_date is not None:
                resolution_date_str = datetime.datetime.strftime(resolution_date, "%Y-%m-%d")
                points = self.get_story_points(issue)
                if resolution_date_str in resolved_on_date:
                    resolved_on_date[resolution_date_str] += points
                else:
                    resolved_on_date[resolution_date_str] = points

        return resolved_on_date

    def total_points_timeline(self, sprint_id, board_name):


    @staticmethod
    def to_datetime(str_date):
        """
        Takes the text-based timestamp from the JSON document and converts it to a Python datetime.
        :param str_date: The string version of the date
        :return: a valid Python datetime object
        """
        p = re.compile("(\d+)-(\d+)-(\d+)T(\d+):(\d+):(\d+)\.\d+\+(\d+)")
        m = p.match(str_date)
        return datetime.datetime(
            int(m.group(1)),
            int(m.group(2)),
            int(m.group(3)),
            (int(m.group(4)) + int(m.group(7))) % 24,
            int(m.group(5)),
            int(m.group(6))
        )
Exemple #20
0
class JiraTicket:
    def __init__(self):
        #this list is where you extend, don't touch anything else
        self.employees = [
            employee1, employee2
            #extend here
        ]
        self.new_tickets = {}
        self.old_tickets = {}
        self.closed_tickets = {}
        self.updated_tickets = {}
        self.needs_update = {}
        jira_options = {'server': server, 'verify': False}
        self.jira = JIRA(options=jira_options,
                         basic_auth=('username', 'password'))

    def get_unix_timestamp(self, issue):
        split = issue.fields.updated.split('T')
        split1 = split[1].split('.')

        created = split[0] + " " + split1[0]
        return (datetime.strptime(created, '%Y-%m-%d %H:%M:%S')).strftime('%s')
        #unix_t = timestamp.strftime('%s')

        return int(unix_t)

    def get_new_tickets(self, username):
        JQL = "reporter=" + username + " and status in ('In Progress', open, reopened)" + " order by created desc"
        JQL_query = self.jira.search_issues(JQL, maxResults=50)

        for item in JQL_query:
            self.new_tickets[str(item)] = {}  #TODO: don't need this
            issue = self.jira.issue(str(item))
            comments = self.jira.comments(str(item))

            self.new_tickets[str(item)] = {
                'commentCount': len(issue.fields.comment.comments),
                'timestamp': int(datetime.now().strftime('%s')),
                'latestCommentAuthor': None
            }
            if self.new_tickets[str(item)]['commentCount'] > 0:
                self.new_tickets[str(item)]['latestCommentAuthor'] = comments[
                    -1].raw['updateAuthor']['displayName']
            else:
                self.new_tickets[str(item)]['latestCommentAuthor'] = str(
                    issue.fields.reporter)

    def write_dict_to_file(self, dict):
        pickle.dump(dict, open("old_jira_tickets.p", "wb"))
        pickle.dump(dict, open("backup_old_jira_tickets.p", "wb"))

    def load_dict_from_file(self):
        try:
            self.old_tickets = pickle.load(open("old_jira_tickets.p", "rb"))
        except:
            self.old_tickets = pickle.load(
                open("backup_old_jira_tickets.p", "rb"))

    def compare_old_to_new(self):
        to_delete_from_old = []
        to_delete_from_new = []

        for key in self.old_tickets:
            old_issue = self.old_tickets[key]

            #check for updated tickets
            if key in self.new_tickets.keys():
                if old_issue['commentCount'] == self.new_tickets[key][
                        'commentCount']:
                    if (int(datetime.now().strftime('%s')) -
                            int(old_issue['timestamp'])) > 7 * 24 * 60 * 60:
                        self.needs_update[key] = {}
                        old_issue['timestamp'] = self.new_tickets[key][
                            'timestamp']
                        if old_issue['commentCount'] > 0:
                            self.needs_update[key] = old_issue[
                                'latestCommentAuthor']

                #else it's been updated
                else:
                    self.updated_tickets[key] = {}
                    if old_issue['commentCount'] > 0:
                        self.updated_tickets[key] = old_issue[
                            'latestCommentAuthor']
                    old_issue['commentCount'] = self.new_tickets[key][
                        'commentCount']
                    old_issue['timestamp'] = self.new_tickets[key]['timestamp']
                to_delete_from_new.append(key)

            #else it's closed
            else:
                self.closed_tickets[key] = {}
                self.closed_tickets[key] = old_issue['latestCommentAuthor']
                to_delete_from_old.append(key)

        for key in to_delete_from_old:
            del (self.old_tickets[key])
        for key in to_delete_from_new:
            del (self.new_tickets[key])

        #at this point the only tickets left in new_tickets are newly created -> add them to
        #old_tickets and recently updated
        for key in self.new_tickets.keys():
            ticket = self.new_tickets[key]

            self.old_tickets[key] = {}
            self.old_tickets[key]['commentCount'] = ticket['commentCount']
            self.old_tickets[key]['timestamp'] = ticket['timestamp']
            self.old_tickets[key]['latestCommentAuthor'] = ticket[
                'latestCommentAuthor']

            self.updated_tickets[key] = {}
            self.updated_tickets[key] = ticket['latestCommentAuthor']

    def test(self):
        jt.old_tickets['ticket']['commentCount'] = 5  #create updated ticket
        jt.old_tickets['ticket'][
            'timestamp'] -= 8 * 24 * 60 * 60  #create needs update ticket
        del (jt.new_tickets['ticket'])  #create closed ticket

    def print_all_lists(self):
        print "new tickets"
        for key in self.new_tickets.keys():
            print key
        print "\nold tickets"
        for key in self.old_tickets.keys():
            print key
        print "\nupdated tickets"
        for key in self.updated_tickets.keys():
            print key
        print "\nclosed tickets"
        for key in self.closed_tickets.keys():
            print key


#		print "\nneeds update tickets"
#		for key in self.needs_update.keys():
#			print key
        print "\n"
Exemple #21
0
            },
            'assignee': {
                'name': issue.fields.assignee.name
            } if issue.fields.assignee else None,
            'description': issue.fields.description,
            'priority': {
                'id': issue.fields.priority.id
            },
            'issuetype': {
                'name': issue.fields.issuetype.name
            },
            'comments': None
        }

        comment_list = []
        comments = jira_instance.comments(issue)
        for comment in comments:
            comment_list.append({
                'body': comment.body,
                'author': comment.author.name,
                'created': comment.created
            })
        issue_dict['comments'] = comment_list

        project_data.append(issue_dict)
    with open(filename, 'w') as outfile:
        outfile.write(yaml.dump(project_data, default_flow_style=False))
elif operation == "import":
    with open(filename, 'r') as data_file:
        project_data = yaml.load(data_file)
    for issue_dict in project_data:
Exemple #22
0
class jiramenu():
    user = None
    project = None
    auth = None
    config = None
    debug = False
    r = Rofi()
    issues = []
    rofi_list = []

    def __init__(self, config, debug):
        self.config = config
        self.r.status("starting jiramenu")
        try:
            self.auth = JIRA(config['JIRA']['url'],
                             basic_auth=(config['JIRA']['user'],
                                         config['JIRA']['password']))
        except Exception as error:
            self.r.exit_with_error(str(error))
        self.debug = debug

    def log(self, text):
        if not self.debug:
            return
        print(text)

    def show(self, user):
        self.user = user
        self.project = self.config['JIRA']['project']
        if user:
            self.log(f"show issues for: {self.user}")

        query = self.config['JIRA']['query']
        if user:
            query += f" and assignee = '{user}'"
        if self.project:
            query += f" and project = '{self.project}'"
        self.log(f"Query: {query}")
        if not self.issues:
            self.issues = self.auth.search_issues(query)
            self.boards = self.auth.boards()

        if not self.rofi_list:
            if user:
                self.rofi_list.append("> all")
            else:
                self.rofi_list.append("> mine")
            self.issues.sort(key=lambda x: x.fields.status.id, reverse=False)
            for issue in self.issues:
                labels = ''
                if len(issue.fields.labels):
                    labels = '('
                    for idx, label in enumerate(issue.fields.labels):
                        labels += label
                        if idx != len(issue.fields.labels) -1:
                            labels += ', '
                    labels += ')'
                issuetext = ''
                issueassignee = ''
                initials = '  '
                if issue.fields.assignee:
                    issueassignee = issue.fields.assignee.displayName
                    initials = ''.join([x[0].upper() for x in issueassignee.split(' ')])
                if issue.fields.status.id == str(3):  #id:3 = Work in Progress
                    issuetext = '{WIP}'
                issuekey = issue.key
                issuekey = "{:<9}".format(issuekey)
                status = "{:<24}".format(issue.fields.status.name)

                issueassignee = "{:<20}".format(issueassignee)
                issuetext += f'{issuekey} {status} {initials}     {labels} {issue.fields.summary}'
                self.rofi_list.append(issuetext)

        # print active query plus number of results on top
        index, key = self.r.select(f'{query}[{len(self.rofi_list)}]',
                                   self.rofi_list,
                                   rofi_args=['-i'],
                                   width=100)
        del key
        if index < 0:
            exit(1)
        if index == 0:
            self.issues = []
            self.rofi_list = []
            if user:
                self.show(None)
            else:
                self.show(self.config['JIRA']['user'])
            return
        self.show_details(index, user)

    def addComment(self, ticket_number):
        comment = self.r.text_entry("Content of the comment:")
        if comment:
            # replace @user with [~user]
            comment = re.sub(r"@(\w+)", r"[~\1]", comment)
            self.auth.add_comment(ticket_number, comment)

    def show_details(self, index, user):
        inputIndex = index
        # ticket_number = re.match("IMP-([1-9]|[1-9][0-9])+", self.rofi_list[index]).group(0)
        issue = self.issues[index-1]
        ticket_number = issue.key
        summary = '-'.join(issue.fields.summary.split(' '))
        branch_name= ticket_number + '-' + summary[:33]

        self.log("[details]" + ticket_number)
        issue_description = issue.fields.description

        output = []
        output.append("> show in browser")
        output.append("")
        output.append(f"> copy branch ({branch_name})")
        output.append("")
        output.append("Status: " + self.issues[index - 1].fields.status.name)
        # output.append("Description: " + issue_description)
        description = []
        if issue_description:
            description = issue_description.split('\n')
        for item in description:
            output.append(item)

        if self.auth.comments(ticket_number):
            comment_ids = self.auth.comments(ticket_number)
            for comment_id in comment_ids:
                self.log("comment_id: " + str(comment_id))
                commentauthor = self.auth.comment(ticket_number, comment_id).author.displayName + ':'
                output.append(commentauthor)
                commenttext = self.auth.comment(ticket_number, comment_id).body
                commenttext = commenttext.split('\n')
                for line in commenttext:
                    output.append(line)
        else:
            output.append("no comments")
        output.append("")
        output.append("> add comment")
        output.append("")
        if self.issues[index - 1].fields.assignee:
            output.append("assigned to: " +
                          self.issues[index - 1].fields.assignee.displayName)
        else:
            output.append("> assign to me")

        # if self.issues[index - 1].fields.status.id == str(3):  # WIP
        #     output.append(">>in review")
        # else:
        #     output.append(">>start progress")
        output.append("")
        output.append('< back')
        index, key = self.r.select(ticket_number, output, width=100)
        if index in [-1, len(output) - 1]:
            self.show(user)
            return

        # if index == len(output) - 2:  # move issue to 'In Review'
        #     self.log("[status]"+self.issues[inputIndex - 1].fields.status.name)
        #     self.log("[transitions]")
        #     self.log(self.auth.transitions(ticket_number))
        #     if self.issues[inputIndex - 1].fields.status.id == str(3):  # WIP
        #         for trans in self.auth.transitions(ticket_number):
        #             if trans['name'] == "in Review":
        #                 self.log("move to 'in Review'")
        #                 self.auth.transition_issue(ticket_number, trans['id'])
        #
        #     else:
        #         for trans in self.auth.transitions(ticket_number):
        #             if trans['name'] == "Start Progress":
        #                 self.log("move to 'Start Progress'")
        #                 self.auth.transition_issue(ticket_number, trans['id'])
        #     self.show_details(inputIndex, user)
        #     return

        if index == len(output) - 4:  # add comment
            self.log("[addComment]")
            self.addComment(ticket_number)
            self.show_details(inputIndex, user)
            return

        if index == len(output) - 3:  # assign to me
            self.log("[assign to me]")
            self.auth.assign_issue(ticket_number, self.config['JIRA']['user'])
            self.show_details(inputIndex, user)
            return

        if index == 2:
            pyperclip.copy(branch_name)
            return

        # if index in [3, 4]:
        #     Popen(['notify-send', issue_description, '-t', '30000'])
        #     self.show_details(inputIndex, user)
        #     return

        # show in browser
        self.log("[show in browser]")
        uri = self.auth.issue(ticket_number).permalink()
        Popen(['nohup', self.config['JIRA']['browser'], uri],
              stdout=DEVNULL,
              stderr=DEVNULL)
Exemple #23
0
                ticketCount += 1
                print '(' + str(ticketCount) + '/' + str(
                    numberOfTicketsToResolve) + ')[' + str(
                        ticketResolveCount
                    ) + ']resolved\nNow working on... ' + issue.key
                jiraBeforeMessage = "This is an automated message.\n\n"
                jiraStartMessage += "==========VIRUS TOTAL WAS ABLE TO FIND THE FOLLOWING URLs==========\n\n"
                jiraEndMessage = ''
                URL_List = list()
                Email_list = list()
                textField = issue.fields.description
                textField = textField.replace('|', ' ')

                URL_List.extend(get_all_URLS_in_text(textField))

                comments = jira.comments(issue.key)
                skipIssue = False
                for comment in comments:
                    #print comment.author.name
                    if comment.author.name == 'ltang':
                        skipIssue = True
                        break
                if skipIssue == True:
                    print '\nalready commented for this issue - skipping\n'
                    numberOfTicketsToResolve -= 1
                    ticketCount -= 1
                    continue

                try:
                    attachments = issue.fields.attachment
                    for attachment in attachments:
class JiraManager():
	def __init__(self):
		self.jira_options = {'server': 'https://jira.omnichat.tech/'}
		self.jira = JIRA(options=self.jira_options, auth=('a.balov', 'MBcLySH5'))
		self.df_to_check = pd.DataFrame()
		self.df_to_score = pd.DataFrame()
		self.minimal_scores = 12
		self.bot = TelegramBot1()
		self.engine = create_engine('postgresql+psycopg2://postgres:[email protected]:5432/jetforms_src')
		self.con = psycopg2.connect(database="jetforms_src", 
								  user="******", 
								  password="******",
								  host="172.19.16.130", 
								  port="5432")
		self.df_scores = pd.read_sql('select * from jira_scoretable', self.con)
		self.df_ignorelist = pd.read_sql('select * from jira_excepted_users', self.con)


	def _adress_security_checker(self, adress):
		rtk = ['ROSTELECOM', r'*****@*****.**', 'rostelecom-cc.ru', '*****@*****.**',r'@sibir.rt.ru','*****@*****.**',
			r'@dv.rt.ru', r'@nw.rt.ru','@mail.ntt.ru','@rt.ru', '@RT.RU','@center.rt.ru','@volga.rt.ru','@south.rt.ru']
		for i in rtk:
		    if i in adress:
		        return True
		return False


	def get_mail(self):
		df = pd.DataFrame()
		outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
		inbox = outlook.GetDefaultFolder(6)

		for item in inbox.Items:
			if item.SenderEmailType == 'EX':
				task_customer = item.Sender.GetExchangeUser().PrimarySmtpAddress
			else:
				task_customer = item.SenderEmailAddress
			if self._adress_security_checker(task_customer):
				try:
					if self.attribute_mail_checker(who=task_customer, 
												whom=item.To, 
												theme=self.beautiful_theme(item.subject),
												text = item.body.split("с уважением")[0]):
						df = df.append({'text': item.body,
						                'subj': self.beautiful_theme(item.subject),
						                'cust': task_customer}, 
						                ignore_index=True)
				except:
						print('')
			item.delete()
		self.df_to_check = self.df_to_check.append(df)

    

	def beautiful_theme(self, theme):
		return theme.replace("FW: ", "").replace("FW:", "").replace("RE: ", "").replace("RE:", "")

	#first checking step: customer and mail-adress To
	def attribute_mail_checker(self, who='', whom='', theme='', text=''):
		if '*****@*****.**' in whom:
			for item in self.df_ignorelist['mail']:
				if who == item:
					return False
		else:
			return False

		atr = self.check_task_from_db(self.beautiful_theme(theme))
		if len(atr) != 0:
			self.add_comment_to_issue(str(atr[0]), '#S'+text.split('С уважением')[0])
			return False

		return True


	#second checking step: semantic points
	def semantic_manager(self):

		def splitter(text):
			text = text.lower()
			text = text.split("с уважением")
			text = text[0].replace('_x000d_', '')
			text[0].replace(',', '')
			text[0].replace('.', '')
			text[0].replace('\n', ' ')
			return text.split(' ')

		scoretable = {}
		for ind in self.df_scores.index.values:
			scoretable.update({self.df_scores['text'].iloc[ind]: self.df_scores['score'].iloc[ind]})

		for ind2 in self.df_to_check.index.values:
			item = self.df_to_check['text'].iloc[ind2]
			scores = 0
			for word in splitter(item):
				parsed_str = word.replace('\n', '').replace('.', '').replace(',', '')
				try:
					scores += scoretable[parsed_str]
				except:
					scores += 0
			if scores >= self.minimal_scores:
				self.df_to_score = self.df_to_score.append(self.df_to_check.iloc[ind2])

	def create_jira_issue(self, issue_dict, customer):
		new_issue = self.jira.create_issue(fields=issue_dict)
		self.bot.send_group_message('Вам заявочка пришла, '+shiz(str(new_issue))+': '+issue_dict['summary']+'\n'+customer+' пишет: '+str(new_issue.fields.description))
		return str(new_issue)

	def send_mail_to_user(self, customer, text, mail_theme, att = ''):
		i = Mail(to = customer, 
			copy = '*****@*****.**', 
			theme = mail_theme, 
			body = text, 
			attach = att
			)
		i.send_mail()


	def search_jira_issue(self, id):
		issue = self.jira.issue(id)
		res = {'ID': str(issue),
			 'issuetype': issue.fields.issuetype.name,
			 'status': issue.fields.status.name,
			 'summary': issue.fields.summary,
			 'description': issue.fields.status.description,
			 'created': issue.fields.created[:10],
			 'resolutiondate': str(issue.fields.resolutiondate)[:10],
			'assignee': str(issue.fields.assignee)}
		return res


	def add_task_to_db(self, customer, jira_att):
		jira_att.update({'customer': customer})
		df = pd.DataFrame(jira_att, index=[0])
		df.to_sql('jira_archive', self.engine, schema ='public', if_exists="append", index = False)


	def get_jira_updates(self):
		df_archive = pd.read_sql('''select * from jira_archive where status <> 'Done' 
			and status <> 'Закрыто' ''', self.con)
		df_archive['created'] = pd.to_datetime(df_archive['created']).apply(lambda x: str(x.date()))


		for ind in df_archive.index.values:
			try:
				df_archive['resolutiondate'].iloc[ind] = str(df_archive['resolutiondate'].iloc[ind].date())
			except:
				pass

			issue = self.jira.issue(df_archive['ID'].iloc[ind])
			customer = df_archive['customer'].iloc[ind]
			jira_dict = {'ID': str(issue),
			 'issuetype': issue.fields.issuetype.name,
			 'status': issue.fields.status.name,
			 'summary': issue.fields.summary,
			 'description': issue.fields.status.description,
			 'created': issue.fields.created[:10],
			 'resolutiondate': str(issue.fields.resolutiondate)[:10],
			'assignee': str(issue.fields.assignee)}
			jira_dict.update({'customer': customer})
			jira_dict.update({'comments': self.get_comments(jira_dict['ID'])})
			
			df_to_dict = df_archive.iloc[ind].to_dict()

			if jira_dict['resolutiondate'] != df_to_dict['resolutiondate'] or jira_dict['comments'] != df_to_dict['comments']:
				q = '''DELETE FROM jira_archive WHERE "ID" = '%s' '''% df_archive['ID'].iloc[ind]
				cur = self.con.cursor()
				cur.execute(q)
				self.con.commit()
				to_add = pd.DataFrame(jira_dict, index =[0])
				to_add.to_sql('jira_archive', self.engine, schema ='public', if_exists="append", index = False)

				self.send_mail_to_user(jira_dict['customer'],
					'Обновление по заявке: ' + jira_dict['ID'] + '\n'+ self.beautiful_body(jira_dict)+ '\n\n',
					jira_dict['summary'], self.get_attach_from_jira(jira_dict['ID']))


	def check_task_from_db(self, theme):
		df_search = pd.read_sql('''select "ID" from jira_archive where status <> 'Done'
			AND summary ='%s' ''' %theme, self.con)
		return(df_search['ID'])


	def add_comment_to_issue(self, id, text):
		self.jira.add_comment(str(id), 'Пользователь пишет: '+text)
		self.bot.send_group_message('Пользователь пишет: , '+ text +'\nЗаявочка: '+shiz(str(id)))


	def update_sources(self):
		self.df_scores = pd.read_sql('select * from jira_scoretable', self.con)
		self.df_ignorelist = pd.read_sql('select * from jira_excepted_users', self.con)


	def get_comments(self, id):
		
		comment_text = ''
		issue = self.jira.issue(id)
		comments = self.jira.comments(issue)
		for comment in comments:
			if '#S' in str(comment.body):
				comment_text += str(comment.author)+ ': '+str(comment.body) + '\n'

		return comment_text.replace('Балов Александр: Пользователь пишет','Пользователь пишет')

		

	def look_comments_from_jira(self, id):
		issue = self.jira.issue(id)
		att_list = []
		comments = self.jira.comments(issue)
		for comment in comments:
			att_list.append(str(comment.body).replace('[','').replace(']','').replace('!','').replace('^','').replace(']',''))
		return att_list


	def get_attach_from_jira(self, id):
		issue = self.jira.issue(id)
		att_list = []
		true_list = self.look_comments_from_jira(id)
		for attachment in issue.fields.attachment:
			image = attachment.get()
			filename=attachment.filename
			if filename in true_list:
				completeName = r'C:\Users\Admin\Desktop\Projects\agent messer\sent_mail\\'+filename
				with open(completeName, 'wb') as f:
					f.write(image)	
				att_list.append(completeName)
		return att_list


	def beautiful_body(self, dict):
		outup = ''
		for key in dict.keys():
			outup += str(key) + ': ' + str(dict[key]).replace('#S','') + '\n'
		return outup
	
	def main_input_proccess(self):
		self.update_sources()
		self.get_mail()
		self.semantic_manager()
		if len(self.df_to_score) > 0:
			for ind in self.df_to_score.index.values:
				issue_dict = {'project': 'BI',
							'summary': str(self.df_to_score['subj'].iloc[ind]),
							'description': str(self.df_to_score['text'].iloc[ind]),
							'issuetype': {'name': 'Задача'}}
				current_issue = str(self.create_jira_issue(issue_dict, self.df_to_score['cust'].iloc[ind]))
				self.add_task_to_db(self.df_to_score['cust'].iloc[ind], self.search_jira_issue(current_issue))
				self.send_mail_to_user(self.df_to_score['cust'].iloc[ind], 
					'По вашему обращению зарегистрирована задача: ' + current_issue + '\n\n', 
					self.df_to_score['subj'].iloc[ind])


	def main_output_proccess(self):
		self.get_jira_updates()