Пример #1
0
def main(username, password, origin_project, target_project, verbose):
    jira = JIRA(JIRA_SERVER_URL, basic_auth=(username, password))

    _origin_project_versions = jira.project_versions(origin_project)
    origin_project_versions = [
        version
        for version in _origin_project_versions
        if version_pattern.match(version.name) is not None
    ]

    _target_project_versions = jira.project_versions(target_project)
    target_project_versions = [
        version
        for version in _target_project_versions
        if version_pattern.match(version.name) is not None
    ]

    if verbose:
        click.echo("\nOriginal project versions")
        for version in origin_project_versions:
            click.echo("- {}".format(version.name))

        click.echo("\nTarget project versions")
        for version in origin_project_versions:
            click.echo("- {}".format(version.name))

    # Put diff of original and target
    versions_to_creation = get_versions_for_creation(
        origin_project_versions, target_project_versions
    )
    create_versions(jira, target_project, versions_to_creation)
Пример #2
0
class TestVersions(unittest.TestCase):
    def setUp(self):
        self.jira = JIRA(options=dict(server=TEST_URL, verify=False),
                         basic_auth=(TEST_USERNAME, TEST_PASSWORD))
        self.purge_test_versions()

    def tearDown(self):
        self.purge_test_versions()

    def get_version(self, name):
        versions = [
            _ for _ in self.jira.project_versions('KB') if _.name == 'test-1'
        ]
        return versions[0] if len(versions) == 1 else None

    def purge_test_versions(self):
        for _ in self.jira.project_versions('KB'):
            if _.name.startswith('test-'):
                _.delete()

    def test_create_version(self):
        result = CliRunner().invoke(
            topcli,
            ['version', 'create', 'KB', 'test-1', '-d', 'Some description'])
        self.assertEqual(result.exit_code, 0)

    def test_delete_version(self):
        result = CliRunner().invoke(topcli,
                                    ['version', 'create', 'KB', 'test-1'])
        self.assertEqual(result.exit_code, 0)
        self.assertIsNotNone(self.get_version('test-1'))
        result = CliRunner().invoke(topcli,
                                    ['version', 'delete', 'KB', 'test-1'])
        self.assertEqual(result.exit_code, 0)
        self.assertIsNone(self.get_version('test-1'))

    def test_update_version(self):
        result = CliRunner().invoke(
            topcli, ['version', 'create', 'KB', 'test-1', '-d', 'before'])
        before = self.get_version('test-1')
        self.assertEqual(before.description, 'before')
        result = CliRunner().invoke(
            topcli, ['version', 'update', 'KB', 'test-1', '-d', 'after'])
        self.assertEqual(result.exit_code, 0)
        after = self.get_version('test-1')
        self.assertEqual(after.description, 'after')

    def test_search_version(self):
        result = CliRunner().invoke(
            topcli,
            ['version', 'create', 'KB', 'test-1', '-d', 'Some description'])
        result = CliRunner().invoke(topcli, ['version', 'search', 'KB'])
        self.assertEqual(result.exit_code, 0)
        self.assertIn('test-1', result.output)
        self.assertIn('Some description', result.output)
Пример #3
0
def jira_prj_versions(project, version_range):
    (from_str, to_str) = version_range.split('-', 1)
    from_ver = parse_version(from_str)
    to_ver = parse_version(to_str)
    if from_ver == None or to_ver == None:
        raise ValueError("Cannot parse range %s" % version_range)
    if type(from_ver) != type(to_ver):
        raise ValueError("Inconsistent types in range %s" % version_range)

    jira = JIRA(server="https://jira.opendaylight.org")
    prj = jira.project(project)

    versions = set()
    for version in jira.project_versions(prj):
        name = version.name
        ver = parse_version(name)
        if type(ver) == type(from_ver) and ver >= from_ver and ver <= to_ver:
            versions.add(name)

    if len(versions) == 0:
        raise ValueError("No versions selected for project %s range %s" %
                         (project, version_range))

    versions = list(versions)
    versions.sort()
    versions = ", ".join(versions)

    return (jira, prj, from_str, to_str, versions)
Пример #4
0
    def GET(self):
        gl.GL_WEBINPUT = web.input()
        projectname = gl.GL_WEBINPUT.product_name
        productline_list = gl.GL_DB.query("select distinct version from crashinfo where appName='" + projectname + "'")
        # select *, count(distinct name) from table group by name
        result = []
        result_g_version = []
        result_jira_version = []
        #jira = JIRA(server='http://127.0.0.1:1194', basic_auth=('wangyang', 'qwerty123456'))
        jira = JIRA(server=jira_server, basic_auth=(user_accout, user_pass))

        for name in project:
            if name in projectname:
                jira_name = project[name]["projectname"]

        versions = jira.project_versions(jira.project(jira_name))

        for item in productline_list:
            item_dict = {"game_version": item.version}
            result_g_version.append(item_dict)

        [result_jira_version.append({"jira_version": v.name}) for v in reversed(versions)]

        result.append(result_g_version)
        result.append(result_jira_version)

        encodedjson = json.dumps(result)

        return encodedjson
Пример #5
0
class TestVersions(unittest.TestCase):
    def setUp(self):
        self.jira = JIRA(options=dict(server=TEST_URL, verify=False), basic_auth=(TEST_USERNAME, TEST_PASSWORD))
        self.purge_test_versions()

    def tearDown(self):
        self.purge_test_versions()

    def get_version(self, name):
        versions = [_ for _ in self.jira.project_versions('KB') if _.name == 'test-1']
        return versions[0] if len(versions) == 1 else None

    def purge_test_versions(self):
        for _ in self.jira.project_versions('KB'):
            if _.name.startswith('test-'):
                _.delete()

    def test_create_version(self):
        result = CliRunner().invoke(topcli, ['version', 'create', 'KB', 'test-1', '-d', 'Some description'])
        self.assertEqual(result.exit_code, 0)

    def test_delete_version(self):
        result = CliRunner().invoke(topcli, ['version', 'create', 'KB', 'test-1'])
        self.assertEqual(result.exit_code, 0)
        self.assertIsNotNone(self.get_version('test-1'))
        result = CliRunner().invoke(topcli, ['version', 'delete', 'KB', 'test-1'])
        self.assertEqual(result.exit_code, 0)
        self.assertIsNone(self.get_version('test-1'))

    def test_update_version(self):
        result = CliRunner().invoke(topcli, ['version', 'create', 'KB', 'test-1', '-d', 'before'])
        before = self.get_version('test-1')
        self.assertEqual(before.description, 'before')
        result = CliRunner().invoke(topcli, ['version', 'update', 'KB', 'test-1', '-d', 'after'])
        self.assertEqual(result.exit_code, 0)
        after = self.get_version('test-1')
        self.assertEqual(after.description, 'after')

    def test_search_version(self):
        result = CliRunner().invoke(topcli, ['version', 'create', 'KB', 'test-1', '-d', 'Some description'])
        result = CliRunner().invoke(topcli, ['version', 'search', 'KB'])
        self.assertEqual(result.exit_code, 0)
        self.assertIn('test-1', result.output)
        self.assertIn('Some description', result.output)
Пример #6
0
def get_releases(client):
    jira = JIRA(client.url,
                basic_auth=(client.username, client.get_password()))

    for project in jira.projects():
        clear_query = Release.objects.filter(client=client).filter(
            project=project.key)
        existing_versions = []
        for version in jira.project_versions(project):
            if version.archived or version.released:
                continue
            existing_versions.append(version.name)

            release = (Release.objects.filter(client=client).filter(
                project=project.key).filter(version=version.name)).first()

            if not release:
                release = Release()
                print(project.key, version.name)
                release.project = project.key
                release.version = version.name
                release.client = client
                release.save()

            release.statuses.all().delete()

            issues = jira.search_issues("project=%s AND fixVersion=%s" %
                                        (version.projectId, version.id))
            for key, value in STATUSES.items():
                statuses = value['statuses']
                found_issues = [
                    issue for issue in issues
                    if issue.fields.status.name in statuses
                ]

                release_status = ReleaseStatus()
                release_status.release = release
                release_status.status = key
                release_status.style = value['style']
                release_status.count = len(found_issues)
                release_status.save()
        clear_query.exclude(version__in=existing_versions).all().delete()
Пример #7
0
class Api(object):
    versions = None

    def __init__(self):
        self.j = JIRA(
            server=settings.JIRA_URL,
            basic_auth=(settings.JIRA_USERNAME, settings.JIRA_PASSWORD),
        )
        self.project = self.j.project(settings.JIRA_PROJECT)

    def get_versions(self):
        return self.j.project_versions(self.project)

    def get_issues(self, version_name, username):
        if self.versions is None:
            self.versions = self.get_versions()

        version = next(
            (v for v in self.versions
             if version_name in v.name
             and '1C' not in v.name
             and '1С' not in v.name
             and 'WWW' not in v.name
             ), None
        )
        if version is None:
            return []
        else:
            jql = 'project=VIP ' \
                  'AND resolution=Unresolved ' \
                  'AND fixVersion="{fixVersion}"' \
                  'AND assignee in ({assignee})'.format(
                fixVersion=version.name,
                assignee=username,
            )
            return self.j.search_issues(jql)
Пример #8
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)
Пример #9
0
class JiraAPI:
    """
    Jira client has no documentation, so if you need one, use one for REST API:
    https://developer.atlassian.com/cloud/jira/platform/rest/v3/
    """
    def __init__(self, settings: Settings):
        self._settings = settings
        self.transition = settings.jira.transition
        self.release_task = settings.jira.release_task
        self.release_task_name = self.release_task.name.format(
            version=settings.version, component=self.release_task.component)

        self._api = JIRA(
            {"server": settings.jira.connection.server},
            basic_auth=(settings.jira.connection.user,
                        settings.jira.connection.token),
        )

    def _create_version(self, project: Project):
        proposed_name = "Hotfix" if self._settings.version.minor > 0 else "Release"
        user_input = input(f"Input new Jira version name: [{proposed_name}]: ")
        name = user_input if user_input else proposed_name

        return self._api.create_version(name,
                                        project,
                                        startDate=_get_formatted_date())

    def _select_version(self, project: Project,
                        unreleased_versions) -> Optional[Version]:
        print("Jira versions:")
        print("1) Skip")
        print("2) Create new")
        print("or select existing one:")

        unreleased_versions = {
            idx: version
            for idx, version in enumerate(unreleased_versions, 3)
        }

        for idx, version in unreleased_versions.items():
            print(f"{idx}) {version.name}")

        user_input = 0
        valid_choices = list(range(1, len(unreleased_versions) + 3))

        while user_input not in valid_choices:
            try:
                user_input = int(
                    input(
                        "\nChoose which Jira version use for this release: "))
            except Exception:
                continue

        if user_input == 1:
            return None
        elif user_input == 2:
            return self._create_version(project)
        else:
            return unreleased_versions[user_input]

    def get_version(self) -> Optional[Version]:
        print_title(f"Searching for Jira release version")
        project = self._api.project(self.release_task.project)

        unreleased_versions = [
            v for v in self._api.project_versions(project) if not v.released
        ]

        return self._select_version(project, unreleased_versions)

    def _get_jira_release_unfinished_tasks(self, version: Version):
        """
        To check that all tasks in Jira release is finished
        select them using jql
        """
        final_statuses = '", "'.join(self.transition.child_final_statuses)
        types_to_skip = '", "'.join(self.transition.child_task_types_to_skip)

        return self._api.search_issues(
            f'project = "{self.release_task.project}"'
            f' AND fixVersion = "{version.name}"'
            f' AND fixVersion in unreleasedVersions("{self.release_task.project}")'
            f' AND status NOT IN ("{final_statuses}")'
            f' AND type NOT IN ("{types_to_skip}")')

    def _get_transition(self, issue, transition_name):
        transitions = [
            t for t in self._api.transitions(issue)
            if t["name"].lower() == transition_name.lower()
        ]
        if not transitions:
            return None

        return transitions[0]

    def release_version(self, release_task_key: str):
        print_title(
            f"Releasing Jira version of release task {release_task_key}")
        release_task = self._api.issue(release_task_key)

        for version in release_task.fields.fixVersions:
            version: Version
            print(f'Checking Jira release version: "{version.name}"...')

            if version.released:
                print_error("Version is already released")
                continue

            unfinished_tasks = self._get_jira_release_unfinished_tasks(version)
            if unfinished_tasks:
                tasks_str = ", ".join([i.key for i in unfinished_tasks])
                print_error(
                    f'Can\'t release Jira version: "{version.name}", it has unfinished tasks: {tasks_str}'
                )
                continue

            print("Jira version is safe to release, releasing...", end=" ")
            version.update(released=True, releaseDate=_get_formatted_date())
            print("Ok!")

    def _add_to_release_version(self, version: Version, release_task_key: str):
        issue = self._api.issue(release_task_key)
        issue.add_field_value("fixVersions", {"name": version.name})

    def make_links(self, version: Optional[Version], release_task_key,
                   related_keys):
        version_name = version.name if version else "-"
        print_title(f"Linking tasks found in release branch"
                    f" to release task ({release_task_key})"
                    f' and to Jira version "{version_name}"')
        if version:
            self._add_to_release_version(version, release_task_key)

        print(f"Linking {len(related_keys)} tasks:")
        partial_make_links = partial(self._make_links, version,
                                     release_task_key)
        with ThreadPool(5) as pool:
            pool.map(partial_make_links, related_keys)

    def _make_links(self, version: Optional[Version], release_task_key: str,
                    child_task_key: str):
        print(f"* {child_task_key}")
        self._api.create_issue_link(self.release_task.link_type,
                                    release_task_key, child_task_key)
        if version:
            self._add_to_release_version(version, child_task_key)

    def make_release_task(self):
        print_title("Creating Jira release task")
        extra_fields = {"components": [{"name": self.release_task.component}]}

        issue = self._api.create_issue(
            project=self.release_task.project,
            summary=self.release_task_name,
            issuetype={"name": self.release_task.type},
            **extra_fields,
        )
        print(f"Created Jira release task: {issue.key}")
        return issue.key

    def get_release_task(self):
        print_title("Searching for Jira release task")
        query = (f'project = "{self.release_task.project}"'
                 f' AND summary ~ "{self.release_task_name}"'
                 f' AND type = "{self.release_task.type}"')
        found_issues = self._api.search_issues(query)

        if not found_issues:
            print("Did not find existing release task")
            return self.make_release_task()

        if len(found_issues) > 1:
            issues_str = ", ".join([i.key for i in found_issues])
            print_error(
                f"Your release task has not unique name, fix it before using this functionality,"
                f" found issues: {issues_str}")
            exit(1)

        release_issue = found_issues[0]
        print(f"Found Jira release task: {release_issue.key}")
        return release_issue.key

    def mark_release_task_done(self, release_task_key):
        print_title(
            f'Transition release task "{release_task_key}" from "{self.transition.release_from_status}" to "{self.transition.release_to_status}"'
        )

        release_issue = self._api.issue(release_task_key)
        print_title(
            f'Current release task status is "{release_issue.fields.status}"')

        if (release_issue.fields.status.name.lower() !=
                self.transition.release_from_status.lower()):
            print_error(
                f'Release task "{release_task_key}" has inproper status')
            return

        transition = self._get_transition(release_issue,
                                          self.transition.release_to_status)

        if not transition:
            print_error(
                f'Release task "{release_task_key}" has no transition to "{self.transition.release_to_status}"'
            )
            return

        self._api.transition_issue(release_issue, transition["id"])

        print(
            f'Release task {release_issue.key} has been transited to status "{transition["name"]}"'
        )

    def mark_children_tasks_done(self, release_task_key):
        print_title(
            f'Transition children of "{release_task_key}" from "{self.transition.child_from_status}" to "{self.transition.child_to_status}"'
        )

        query = (f'issue in linkedIssues("{release_task_key}")'
                 f' AND status = "{self.transition.child_from_status}"')
        found_issues = self._api.search_issues(query)

        to_status = self.transition.child_to_status.lower()

        if not found_issues:
            print("Did not find any task for transition")
            return

        for issue in found_issues:
            transition = self._get_transition(issue, to_status)
            if not transition:
                print_error(
                    f'Issue "{issue.key}" does not have transition to status "{self.transition.child_to_status}"'
                )
                continue

            self._api.transition_issue(issue, transition["id"])
            print(
                f'Task {issue.key} has been transited to status "{transition["name"]}"'
            )
Пример #10
0
class JiraSession(object):
    def __init__(self, server, account, password, verify=True):
        """
        Init Jira Session
        :param server:
        :param account:
        :param password:
        :param verify:
        """
        self.__server = server
        self.__account = account
        self.__password = password
        self.__jira_opts = {
            'server': self.__server,
            'verify': verify,
        }
        self.__session = JIRA(self.__jira_opts,
                              basic_auth=(self.__account, self.__password))

    def __enter__(self):
        assert self.__session.current_user() == self.__account
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.__session.close()

    def get_user(self):
        """
        Get jira user
        :return:
        """
        logging.info(u'Get JIRA Current User')
        return self.__session.current_user()

    def search_issues(self, jql):
        """
        Search issues via JQL
        :param jql:
        :return:
        """
        logging.info(u'JIRA Search: %s' % jql)
        return self.__session.search_issues(jql_str=jql,
                                            maxResults=128,
                                            json_result=True)

    # def get_issue_count(self, jql: str, issue_summary: dict, issue_key: str):
    #     """
    #     Search issues via JQL and return count
    #     :param jql:
    #     :param issue_summary:
    #     :param issue_key:
    #     :return:
    #     """
    #     logging.info(u'JIRA Issue Count: %s' % jql)
    #     issue_summary[issue_key] = int(self.search_issues(jql).get('total'))
    #     return True

    def get_projects(self):
        """
        Get jira projects
        :return: <key, name, id>
        """
        logging.info(u'Get JIRA Projects')
        return self.__session.projects()

    def get_sprints(self):
        """
        Get jira sprints
        :return: <name, id>
        """
        logging.info(u'Get JIRA Sprints')
        jira_sprints = list()
        for board in self.__session.boards():
            _sprints = self.__session.sprints(board.id)
            jira_sprints = jira_sprints + _sprints
        return jira_sprints

    def get_issue_fields(self):
        """
        Get jira fields
        :return: [{'name':'','id':''}]
        """
        logging.info(u'Get JIRA Fields')
        _fields = list()
        for _field in self.__session.fields():
            _fields.append({'name': _field['name'], 'id': _field['id']})
        return _fields

    def get_issue_types(self):
        """
        Get jira issue types
        :return: <name, id>
        """
        logging.info(u'Get JIRA Issue Types')
        return self.__session.issue_types()

    def get_issue_statuses(self):
        """
        Get issue statuses
        :return: <name, id>
        """
        logging.info(u'Get JIRA Issue Statuses')
        return self.__session.statuses()

    def get_project_versions(self, pid: str):
        """
        Get project versions
        :param pid:
        :return: [<name, id>]
        """
        logging.info(u'Get JIRA Project %s Versions' % pid)
        return self.__session.project_versions(project=pid)
Пример #11
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
Пример #12
0
    def create_bug_issue(self,
                         channel,
                         summary,
                         description,
                         component,
                         version,
                         labels,
                         user=JIRA_USER,
                         passwd=JIRA_PASS,
                         project=JIRA_PROJECT):
        """
        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
        """
        if user and passwd and project:
            try:
                jira = JIRA(server='https://issues.voltdb.com/',
                            basic_auth=(user, passwd))
            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 bug with same summary
        existing = jira.search_issues('summary ~ \'%s\'' % summary)
        if len(existing) > 0:
            # Already reported
            self.logger.info('OLD: Already reported issue with summary "' +
                             summary + '"')
            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)
        for v in versions:
            if v.name == version:
                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']

        new_issue = jira.create_issue(fields=issue_dict)
        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)
Пример #13
0
        item = top_ten.pop()
        prj_name = item[0]
        issue_count = item[1]
        print("Project name : {name}, Assigned Count : {count}".format(
            name=prj_name, count=issue_count))

        #project 상세 정보 표출
        if prj_name == "BTVD":
            prj = jira.project(prj_name)
            print("=" * 30)
            print("Project name : %s" % prj.name)
            print("Project leader name : %s" % prj.lead.displayName)
            components = jira.project_components(prj)
            print("Components : " + str([c.name for c in components]))
            print("Roles : " + str(jira.project_roles(prj)))
            versions = jira.project_versions(prj)
            print("Versions : " + str([v.name for v in reversed(versions)]))
            print("=" * 30)
        else:
            continue

    break_loop = False
    for issue in issues:
        print("Issue summary : " + issue.fields.summary)
        watcher = jira.watchers(issue)
        if watcher.watchCount > 1:
            print("Issue has {} watchers(s)".format(watcher.watchCount))
            for watcher in watcher.watchers:
                print(watcher)
                print(watcher.emailAddress)
                break_loop = True
Пример #14
0
                                   user="******",
                                   passwd="jabra2020",
                                   database="projects")

    c = mydb.cursor()
    alt = list()
    c.execute("SELECT * FROM projects WHERE status = 'Active' AND jama != 0")
    myresult = c.fetchall()
    for project in myresult:
        alt.append(project[0])
    c.close()
    mydb.close()

    # Check the project status
    for project in projects:
        if project.key in alt:
            logData("Checking status for " + project.name + "")
            checkStatus(project)
            logData("Done checking status for " + project.name + "")
            logData("Processing data for " + project.name + "")
            logData("Fetching periods for " + project.name + "")
            project.periods = jira.project_versions(project.key)
            logData("Fetching meta for " + project.name + "")
            queryJiraMeta(project)
            logData("Fetching data for " + project.name + "")
            queryJira(project)
    logData("Saving projects in db")
    saveProjects()
    elapsed = timeit.default_timer() - start_time
    logData("Done fetching & parsing data from Jira. Elapsed time: %s" %
            elapsed)
Пример #15
0
class Jira(object):
    """Python-Jira API class"""
    def __init__(self, args):
        """Init"""
        self.jira_server = 'https://company.jira.net'
        self.jira = JIRA('%s' % self.jira_server, basic_auth=(args.jira_user,args.jira_pswd))

        # Arguments
        self.release_name = args.release_name
        self.release_date = args.release_date
        self.project = args.project
        self.status = args.status
        self.fix_version = args.fix_version
        self.tkts_resolved = args.tkts_resolved
        self.verbose = args.verbose

        self.issues = []

    def search_issues(self):
        """Return issues searched with JQL"""
        search_str = 'project = %s AND status = %s AND fixVersion = %s order by lastViewed DESC' % \
                     (self.project, self.status, self.fix_version)

        return self.jira.search_issues(search_str)

    def is_version_exist(self):
        """Check if version exist"""
        # New version is in the last element of the list
        version = self.jira.project_versions(self.project)[-1]

        if str(version) == self.release_name:
            return True
        else:
            return False

    def create_version(self):
        """Create Jira release version"""
        descrp = []

        # Adding tickets summary to release description
        for tkt in self.issues:
            descrp.append((tkt.fields.summary).encode('UTF8'))

        descrp = '. '.join(descrp)

        self.jira.create_version(self.release_name,\
                                 self.project,\
                                 description=descrp,\
                                 releaseDate=self.release_date,\
                                 startDate=None,\
                                 archived=False,\
                                 released=True)

    def tickets_resolved_log(self):
        """Create a file with resolved tickets summary"""
        version = self.jira.project_versions(self.project)[-1]
        release_url = '%s/projects/%s/versions/%s/tab/release-report-all-issues/' % (self.jira_server, self.project, str(version.id))
        
        hdr_str = "======================================================\n" \
                  "The new Fixes or Features added in this release are:\n" \
                  "\n" \
                  "%s\n\n" % release_url 

        tail_str = "======================================================"

        with open(self.tkts_resolved, "a") as tkts_file:
            tkts_file.write(hdr_str)
            for tkt in self.issues:
                tkts_file.write("%s: %s \n" % (tkt.key, tkt.fields.summary))
            tkts_file.write(tail_str)

    def update_issue_fix_version(self):
        """ Update fixVersion of tickets"""
        fixVersion = []

        fixVersion.append({'name': self.release_name})
        for tkt in self.issues:
            tkt.update(fields={'fixVersions': fixVersion})

    def create_release(self):
        """ Workflow to create release:
            1. Search tickets for release
            2. Create Jira release version
            3. Update fixVersion of tickets
            4. Parse tickets resolved to file for release announcment
        """
        self.issues = self.search_issues()

        version_exist = self.is_version_exist()
        if version_exist == False:
            self.create_version()

        self.update_issue_fix_version()

        self.tickets_resolved_log()

    def command(self, args):
        """Commands for Jira release"""
        if 'create_release' in args.command:
            self.create_release()

        elif 'close_release' in args.command:
            self.close_release()
Пример #16
0
class jira_handler:
    """
    Jira处理类。
    【备注】:目前存在Jira方法与Issue对象混合在一起的不足。
    【改进】:将Jira方法与Issue对象实体分离,各自进行类定义。
    """

    def __init__(self, project_name):
        self.mongo_db = mongodb_class.mongoDB(project_name)
        self.jira = JIRA('http://172.16.60.13:8080', basic_auth=('shenwei','sw64419'))
        self.gh = GreenHopper({'server': 'http://172.16.60.13:8080'}, basic_auth=('shenwei', 'sw64419'))
        self.name = project_name
        self.project = self.jira.project(self.name)
        self.pj_name = u"%s" % self.project.name
        self.pj_manager = u"%s" % self.project.lead.displayName
        """获取项目版本信息
        """
        _versions = self.jira.project_versions(self.name)
        self.version = {}
        self.sprints = self._get_sprints()
        for _v in _versions:
            _key = (u"%s" % _v).replace('.', '^')
            if not self.version.has_key(_key):
                self.version[_key] = {}
            self.version[_key][u"id"] = _v.id
            self.version[_key]['startDate'] = ""
            self.version[_key]['releaseDate'] = ""
            if 'startDate' in dir(_v):
                self.version[_key]['startDate'] = _v.startDate
            if 'releaseDate' in dir(_v):
                self.version[_key]['releaseDate'] = _v.releaseDate
            self.mongo_db.handler("project", "update",
                                  {"version": _key}, dict({"version": _key}, **self.version[_key]))
        self.issue = None

    def _get_board(self):
        _boards = self.jira.boards()
        for _b in _boards:
            if self.name.upper() in _b.name.upper():
                return _b.id
        return None

    def transDate(self, str):
        print("---> transDate [%s] <---" % str)
        if str != None and str != u'无':
            _s = str.\
                replace(u'十一月', '11').\
                replace(u'十二月', '12').\
                replace(u'一月', '1').\
                replace(u'二月', '2').\
                replace(u'三月', '3').\
                replace(u'四月', '4').\
                replace(u'五月', '5').\
                replace(u'六月', '6').\
                replace(u'七月', '7').\
                replace(u'八月', '8').\
                replace(u'九月', '9').\
                replace(u'十月', '10')
            _time = time.strptime(_s, '%d/%m/%y')
            return time.strftime('%Y-%m-%d', _time)
        else:
            return ""

    def _get_sprints(self):
        """
        获取看板内sprint列表
        :return: sprint列表 [ name, startDate, endDate, state ]
        """
        _list = []
        _b_id = self._get_board()
        if type(_b_id) is not types.NoneType:
            _sprints = self.jira.sprints(_b_id)
            for _s in _sprints:
                _sprint = self.jira.sprint(_s.id)
                _data = {'name': _s.name,
                         'startDate': self.transDate(_sprint.startDate.split(' ')[0]),
                         'endDate': self.transDate(_sprint.endDate.split(' ')[0]),
                         'state': _s.state
                         }
                _list.append(_data)
            return _list
        return None

    def get_sprints(self):
        return self.sprints

    def get_current_sprint(self):
        """
        获取本阶段sprint名称
        :return: 返回状态为ACTIVE的sprint的名称
        """
        if type(self.sprints) is not types.NoneType:
            for _s in self.sprints:
                if _s['state'] == 'ACTIVE':
                    return (_s['name'], _s['startDate'], _s['endDate']), _next
                _next = (_s['name'], _s['startDate'], _s['endDate'])
        return None

    def get_sprint(self):
        """
        获取当前Issue的sprint定义
        :return: sprint定义
        """
        if "customfield_10501" in self.issue.raw['fields'] and \
                type(self.issue.fields.customfield_10501) is not types.NoneType:
            return u'%s' % (",".join(item.split('name=')[1].split(',')[0]
                                      for item in self.issue.fields.customfield_10501))
            # return u'%s' % self.issue.fields.customfield_10501[0].split('name=')[1].split(',')[0]
        return None

    def get_versions(self):
        _v = {}
        for _k in self.version:
            _key = (u"%s" % _k).replace('^', '.')
            _v[_key] = self.version[_k]
        return _v

    def get_pj_info(self):
        return {'pj_name': self.pj_name, 'pj_manager': self.pj_manager}

    def set_issue_by_name(self, issue_id):
        self.issue = self.jira.issue(issue_id)

    def print_green_hopper(self):
        _f = self.gh.fields()
        for __f in _f:
            __cns = __f['clauseNames']
            print('-' * 8)
            for _n in __cns:
                print u"name: %s" % _n
            print "id: ", u"%s" % __f['id']
            print "name: ", u"%s" % __f['name']

    def get_story_point(self):
        """
        获取Issue(story)的预置成本, 1 point = 4 hours
        :return: 预置成本
        """
        if "customfield_10304" in self.issue.raw['fields'] and \
                type(self.issue.fields.customfield_10304) is not types.NoneType:
            return self.issue.fields.customfield_10304
        return None

    def get_task_time(self):
        return {"agg_time": self.issue.fields.aggregatetimeestimate,
                "org_time": self.issue.fields.timeoriginalestimate,
                "spent_time": self.issue.fields.timespent}

    def get_landmark(self):
        if len(self.issue.fields.fixVersions) > 0:
            return u"%s" % self.issue.fields.fixVersions[0]
        if len(self.issue.fields.versions) > 0:
            # print self.show_name(), " version: %s" % self.issue.fields.versions[0]
            return u"%s" % self.issue.fields.versions[0]
        return ""

    def get_desc(self):
        return self.issue.fields.summary

    def show_name(self):
        return str(self.issue)

    def get_type(self):
        return u"%s" % self.issue.fields.issuetype

    def get_status(self):
        """
        获取Issue的状态,待办、处理中、待测试、测试中、完成
        :return:
        """
        return u"%s" % self.issue.fields.status

    def get_subtasks(self):
        """
        收集issue的相关子任务的issue
        :return: 相关issue字典
        """
        link = {}
        if not link.has_key(self.show_name()):
            link[self.show_name()] = []
        _task_issues = self.issue.fields.subtasks
        for _t in _task_issues:
            link[self.show_name()].append(u"%s" % _t)
        return link

    def get_child_requirement(self):

        link = []
        jql = "issue in  childrenOfParentRequirement('%s')" % self.show_name()
        # print jql
        tot = 0
        while True:
            issues = self.jira.search_issues(jql, maxResults=100, startAt=tot)
            for issue in issues:
                link.append(issue.key)
            if len(issues) == 100:
                tot += 100
            else:
                break
        return link

    def get_epic_link(self, jql):

        print(">>> get_epic_link<%s>" % jql)
        total = 0
        _issue_name = self.show_name()
        task_link = {_issue_name: []}
        while True:
            issues = self.jira.search_issues(jql, maxResults=100, startAt=total)
            for issue in issues:
                self.issue = issue
                self.sync_issue()
                """收集epic相关的story和任务"""
                task_link[_issue_name].append(self.show_name())
            if len(issues) == 100:
                total += 100
            else:
                break
        print task_link
        self.set_issue_by_name(_issue_name)
        return task_link

    def get_link(self):
        """
        收集issue的相关issue
        :return: 相关issue字典
        """
        link = {self.show_name(): []}

        """兼容以前: 与story相关的task是通过issulelinks关联的"""
        _task_issues = self.issue.fields.issuelinks
        for _t in _task_issues:
            if "outwardIssue" in dir(_t):
                """该story相关的任务"""
                link[self.show_name()].append(u"%s" % _t.outwardIssue)
            if "inwardIssue" in dir(_t):
                """该story相关的任务"""
                link[self.show_name()].append(u"%s" % _t.inwardIssue)

        """采用synapseRT插件后对需求的管理"""
        _task_issues = self.get_child_requirement()
        for _t in _task_issues:
            link[self.show_name()].append(_t)

        return link

    def sync_issue(self):
        """
        同步issue数据,同时完成重要参量的变更日志。
        :return:
        """
        _components = u"%s" % (', '.join(comp.name for comp in self.issue.fields.components))
        _key = u"%s" % self.show_name()
        _time = self.get_task_time()
        _epic_link = None
        if "customfield_11300" in self.issue.raw['fields'] and \
                type(self.issue.fields.customfield_11300) is not types.NoneType:
            _epic_link = self.issue.raw['fields']["customfield_11300"]
        _issue = {u"%s" % self.show_name(): {
                "issue_type": self.get_type(),
                "created": self.issue.fields.created,
                "updated": self.issue.fields.updated,
                "lastViewed": self.issue.fields.lastViewed,
                "users": self.get_users(),
                "status": self.get_status(),
                "landmark": self.get_landmark(),
                "point": self.get_story_point(),
                "agg_time": _time['agg_time'],
                "org_time": _time['org_time'],
                "summary": self.issue.fields.summary,
                "spent_time": _time['spent_time'],
                "sprint": self.get_sprint(),
                "epic_link": _epic_link,
                "components": _components
            }}
        _old_issue = self.mongo_db.handler("issue", "find_one", {"issue": _key})
        if _old_issue is None:
            self.mongo_db.handler("issue", "update",
                                  {"issue": _key}, dict({"issue": _key}, **_issue[_key]))
        else:
            _change = False
            for _item in ['issue_type','created','updated','users','status',
                          'landmark','point','agg_time','org_time',
                          'summary','spent_time','sprint','epic_link']:
                if _old_issue[_item] != _issue[_key][_item]:
                    _log = {"issue_id": _key, "key": _item,
                            "old": _old_issue[_item], "new": _issue[_key][_item]}
                    self.write_log(_log)
                    _change = True
            _change = True
            if _change:
                self.mongo_db.handler("issue", "update",
                                      {"issue": _key}, dict({"issue": _key}, **_issue[_key]))

    def get_issue_link(self):
        _link = self.get_link()
        print "---> get_issue_link: ", _link
        return _link

    def sync_issue_link(self):
        _key = u"%s" % self.show_name()
        _link = self.get_link()

        print "sync_issue_link: ", _link

        self.mongo_db.handler("issue_link", "update",
                              {"issue": _key}, dict({"issue": _key}, **_link))
        return _link

    def show_issue(self):
        """
        显示issue信息
        :return:
        """
        print(u"[%s]" % self.show_name()),
        print u"类型:%s" % self.get_type(),
        print(u'状态:%s' % self.get_status()),
        print u"里程碑:%s" % self.get_landmark()

    def get_users(self):
        """
        获取访问issue的用户
        2018.3.1:改为 经办人 assignee
        :return:
        watcher = self.jira.watchers(self.issue)
        _user = u"%s" % (', '.join(watcher.displayName for watcher in watcher.watchers))
        """
        if type(self.issue.raw['fields']["assignee"]) is types.NoneType:
            return None
        return (u"%s" % self.issue.raw['fields']["assignee"]['displayName']).replace(' ', '')

    def write_log(self, info):
        print "---<write_log>---: ",info
        self.mongo_db.handler("log", "insert", info)

    def write_worklog(self, info):
        """
        写入或更新 工作日志记录。
        :param info: 新的日志数据
        :return:
        """
        if not info.has_key("comment"):
            return

        _search = {'issue': info['issue'],
                   'id': info['id']}
        self.mongo_db.handler('worklog', 'update', _search, info)

    def clear_worklog(self, worklog_id):
        """
        清除"不存在"的记录!
        :param worklog_id: 存在的worklog_id
        :return:
        """
        _set = {'timeSpentSeconds': 0}
        if len(worklog_id) > 0:
            _search = {"issue": self.show_name(), "id": {"$not": {"$in": worklog_id}}}
        else:
            _search = {"issue": self.show_name()}
        _one = self.mongo_db.handler('worklog', 'find_one', _search)
        if _one is None:
            return
        # self.mongo_db.handler('worklog', 'remove', _search)
        """保留原记录,将其用时值设置为0,以便事后跟踪
        """
        self.mongo_db.handler('worklog', 'update', _search, _set)

    def sync_worklog(self):
        """
        获取指定 issue 的工作日志记录。
        - 2018.4.2:针对以前有的,但现在没有的 日志记录 的处理?!清除其spent时间
        :return:
        """
        worklogs = self.jira.worklogs(self.show_name())
        wl = {}
        _id = []
        for worklog in worklogs:
            wl['issue'] = self.show_name()
            wl['author'] = u'%s' % worklog.author
            wl['comment'] = u'%s' % worklog.comment
            wl['timeSpent'] = worklog.timeSpent
            wl['timeSpentSeconds'] = worklog.timeSpentSeconds
            wl['updated'] = worklog.updated
            wl['created'] = worklog.created
            wl['started'] = worklog.started
            wl['id'] = worklog.id
            _id.append(worklog.id)
            self.write_worklog(wl)
        """同时同步Issue的变动日志"""
        """ 因worklog可随意更改或删除, 有必要实时清除多余的记录!"""
        self.clear_worklog(_id)

        self.sync_changelog()

    def scan_task_by_sprint(self, sprint):
        """
        通过sprint获取Issue,以便获取它们的 工作日志
        :param sprint: 当前的sprint名称
        :return: Issue列表
        """
        jql_sql = u'project=%s AND Sprint = "%s" ORDER BY created DESC' %\
                  (self.name, sprint)
        total = 0
        tasks = []
        while True:
            issues = self.jira.search_issues(jql_sql, maxResults=100, startAt=total)
            for issue in issues:
                self.issue = issue
                """同步issue"""
                self.sync_issue()
                self.sync_worklog()
                self.sync_issue_link()
                tasks.append(self.show_name())
            if len(issues) == 100:
                total += 100
            else:
                break
        return tasks

    def scan_epic(self, bg_date):
        """
        扫描project收集epic信息
        :param bg_date: 起始日期,如 2018-1-31
        :param issue_type:指定issue类型
        :return: 按issue类型进行统计值kv_sum,issue链kv_link,相关任务链task_link
        """
        jql_sql = u'project=%s AND issuetype=epic AND created >= %s ORDER BY created DESC' % (
            self.name, bg_date)
        total = 0
        story_link = []

        while True:
            issues = self.jira.search_issues(jql_sql, maxResults=100, startAt=total)
            for issue in issues:
                self.issue = issue
                """同步issue"""
                # self.show_issue()
                self.sync_issue()
                """收集epic相关的story和任务"""
                _jql = u'project=%s AND "Epic Link"=%s AND created >= %s ORDER BY created DESC' % \
                       (self.name, self.show_name(), bg_date)
                _link = self.get_epic_link(_jql)
                """同步epic的link"""
                # print "--> epic link: ", dict({"issue": self.show_name()}, **_link)
                self.mongo_db.handler("issue_link", "update",
                                      {"issue": self.show_name()},
                                      dict({"issue": self.show_name()}, **_link))
                story_link += _link[self.show_name()]

            if len(issues) == 100:
                total += 100
            else:
                break
        return story_link

    def scan_story(self, bg_date):
        """
        按 project 获取其下所有 story 数据。
        :param bg_date: 开始搜索的日期
        :return:
        """
        jql_sql = u'project=%s AND issuetype=story AND created >= %s ORDER BY created DESC' % (
            self.name, bg_date)
        total = 0
        task_link = []

        while True:
            issues = self.jira.search_issues(jql_sql, maxResults=100, startAt=total)
            for issue in issues:
                self.issue = issue
                """同步issue"""
                # self.show_issue()
                self.sync_issue()
                """收集story相关的任务"""
                _link = self.sync_issue_link()
                """同步epic的link"""
                self.mongo_db.handler("issue_link", "update",
                                      {"issue": self.show_name()},
                                      dict({"issue": self.show_name()}, **_link))
                task_link += _link[self.show_name()]

            if len(issues) == 100:
                total += 100
            else:
                break
        return task_link

    def scan_task(self, bg_date):
        """
        按 project 获取其下所有与执行相关的 issue 数据。
        :param bg_date: 开始搜索的日期
        :return:
        """
        jql_sql = u'project=%s AND ( issuetype=task OR' \
                  u' issuetype=任务 OR' \
                  u' issuetype=故障 OR' \
                  u' issuetype=Bug OR' \
                  u' issuetype=Sub-task OR' \
                  u' issuetype=子任务 ) AND' \
                  u' created >= %s ORDER BY created DESC' % (self.name, bg_date)
        print jql_sql
        total = 0
        task_link = []

        while True:
            issues = self.jira.search_issues(jql_sql, maxResults=100, startAt=total)
            for issue in issues:
                self.issue = issue
                """同步issue"""
                self.show_issue()
                self.sync_issue()
                # self.sync_changelog()
                self.sync_worklog()
                task_link.append(self.show_name())
            if len(issues) == 100:
                total += 100
            else:
                break
        return task_link

    def sync_changelog(self):
        """
        获取指定 issue 的 变更日志记录
        :return:
        """
        issue = self.jira.issue(self.show_name(), expand='changelog')
        changelog = issue.changelog
        for history in changelog.histories:
            for item in history.items:
                _data = {'issue': self.show_name(),
                         'field': item.field,
                         'author': u"%s" % history.author,
                         'date': history.created,
                         'old': getattr(item, 'fromString'),
                         'new': getattr(item, 'toString')
                         }
                if self.mongo_db.handler('changelog', 'find_one', _data) is None:
                    self.mongo_db.handler('changelog', 'insert', _data)
Пример #17
0
    def report_issue(self, build):
        try:
            jira = JIRA(server='https://issues.voltdb.com/', basic_auth=(JIRA_USER, JIRA_PASS))
        except:
            logging.exception('Could not connect to Jira')
            return

        build_report_url = self.jhost + '/job/' + job + '/' + str(build) + '/api/python'
        build_report = eval(self.read_url(build_report_url))
        build_url = build_report.get('url')
        build_result = build_report.get('result')

        if build_result == 'SUCCESS':  # only generate Jira issue if the test fails
            print 'No new issue created. Build ' + str(build) + 'resulted in: ' + build_result
            return

        summary_url = self.jhost + '/job/' + job + '/' + str(build) + '/artifact/tests/sqlgrammar/summary.out'
        summary_report = self.read_url(summary_url)

        pframe_split = summary_report.split('Problematic frame:')
        pframe_split = pframe_split[1].split('C')
        pframe_split = pframe_split[1].split(']')
        pframe_split = pframe_split[1].split('#')
        pframe = pframe_split[0].strip()

        summary = job + ':' + str(build) + ' - ' + pframe
        # search_issues gets a parsing error on (), so escape it.
        existing = jira.search_issues('summary ~ \'%s\'' % summary.replace('()','\\\\(\\\\)',10))
        if len(existing) > 0:
            print 'No new Jira issue created. Build ' + str(build) + ' has already been reported.'
            return 'Already reported'

        old_issue = ''
        existing = jira.search_issues('summary ~ \'%s\'' % pframe_split[0].strip().replace('()','\\\\(\\\\)',10))
        for issue in existing:
            if str(issue.fields.status) != 'Closed' and u'grammar-gen' in issue.fields.labels:
                old_issue = issue

        build_artifacts = build_report.get('artifacts')[0]
        pid_fileName = build_artifacts['fileName']
        pid_url = build_url + 'artifact/' + pid_fileName

        query_split = summary_report.split('(or it was never started??), after SQL statement:')
        crash_query = query_split[1]

        hash_split = summary_report.split('#', 1)
        hash_split = hash_split[1].split('# See problematic frame for where to report the bug.')
        sigsegv_message = hash_split[0] + '# See problematic frame for where to report the bug.\n#'

        description = job + ' build ' + str(build) + ' : ' + str(build_result) + '\n' \
            + 'Jenkins build: ' + build_url + ' \n \n' \
            + 'DDL: ' + 'https://github.com/VoltDB/voltdb/blob/master/tests/sqlgrammar/DDL.sql' + ' \n \n' \
            + 'hs_err_pid: ' + pid_url + ' \n \n' \
            + 'SIGSEGV Message: \n' + '#' + sigsegv_message + ' \n \n' \
            + 'Query that Caused the Crash: ' + crash_query
        description = description.replace('#', '\#')

        labels = ['grammar-gen']

        component = 'Core'
        components = jira.project_components(JIRA_PROJECT)
        jira_component = {}
        for c in components:
            if c.name == component:
                jira_component = {
                    'name': c.name,
                    'id': c.id
                }
                break

        current_version_raw = str(self.read_url('https://raw.githubusercontent.com/VoltDB/voltdb/master/version.txt'))
        current_version_float = float(current_version_raw)
        current_version = 'V' + current_version_raw
        current_version = current_version.strip()
        next_version = current_version_float + .1
        next_version = str(next_version)
        next_version = 'V' + next_version
        next_version = next_version[:4]

        jira_versions = jira.project_versions(JIRA_PROJECT)
        this_version = {}
        new_version = {}

        for v in jira_versions:
            if str(v.name) == current_version:
                this_version = {
                    'name': v.name,
                    'id': v.id
                }
            if str(v.name) == next_version:
                new_version = {
                    'name': v.name,
                    'id': v.id
                }

        issue_dict = {
            'project': JIRA_PROJECT,
            'summary': summary,
            'description': description,
            'issuetype': {'name': 'Bug'},
            'priority': {'name': 'Blocker'},
            'labels': labels,
            'customfield_10430': {'value': 'CORE team'},
            'components': [jira_component]
        }

        if new_version:
            issue_dict['versions'] = [new_version]
            issue_dict['fixVersions'] = [new_version]

        elif this_version:
            issue_dict['versions'] = [this_version]
            issue_dict['fixVersions'] = [this_version]

        if old_issue:
            new_comment = jira.add_comment(old_issue, description)
            print 'JIRA-action: New comment on issue: ' + str(old_issue) + ' created for failure on build ' + str(build)
        else:
            new_issue = jira.create_issue(fields=issue_dict)
            print 'JIRA-action: New issue ' + new_issue.key + ' created for failure on build ' + str(build)
Пример #18
0
class ViraAPI():
    '''
    This class gets imported by __init__.py
    '''
    def __init__(self):
        '''
        Initialize vira
        '''

        # Load user-defined config files
        file_servers = vim.eval('g:vira_config_file_servers')
        file_projects = vim.eval('g:vira_config_file_projects')
        try:
            self.vira_servers = load_config(file_servers)
            self.vira_projects = load_config(file_projects)
        except:
            print(f'Could not load {file_servers} or {file_projects}')

        self.userconfig_filter_default = {
            'assignee': '',
            'component': '',
            'fixVersion': '',
            'issuetype': '',
            'priority': '',
            'project': '',
            'reporter': '',
            'status': '',
            "'Epic Link'": '',
            'statusCategory': ['To Do', 'In Progress'],
            'text': ''
        }
        self.reset_filters()

        self.userconfig_newissue = {
            'assignee': '',
            'component': '',
            'fixVersion': '',
            'issuetype': 'Bug',
            'priority': '',
            'epics': '',
            'status': '',
        }

        self.users = set()
        self.versions = set()
        self.servers = set()
        self.users_type = ''
        self.async_count = 0

        self.versions_hide(True)

    def _async(self, func):
        try:
            func()
        except:
            pass

    def _async_vim(self):
        #  TODO: VIRA-247 [210223] - Clean-up vim variables in python _async
        try:
            if len(vim.eval('s:versions')) == 0:
                vim.command('let s:projects = s:projects[1:]')
                if len(vim.eval('s:projects')) == 0:
                    #  TODO: VIRA-247 [210223] - Check for new projects and versions and start PRIORITY ranking for updates
                    vim.command('let s:vira_async_timer = g:vira_async_timer')
                    self.get_projects()
                self.get_versions()
            else:
                #  self.version_percent(str(vim.eval('s:projects[0]')), str(vim.eval('s:versions[0]')))
                vim.command('let s:versions = s:versions[1:]')

            if self.async_count == 0 and vim.eval(
                    's:vira_async_timer') == 10000:
                self.users = self.get_users()
                self.async_count = 1000
            self.async_count -= 1
        except:
            pass

    def create_issue(self, input_stripped):
        '''
        Create new issue in jira
        '''

        section = {
            'summary':
            parse_prompt_text(input_stripped, '*Summary*', 'Description'),
            'description':
            parse_prompt_text(input_stripped, 'Description', '*Project*'),
            'project':
            parse_prompt_text(input_stripped, '*Project*', '*IssueType*'),
            'issuetype':
            parse_prompt_text(input_stripped, '*IssueType*', 'Status'),
            'status':
            parse_prompt_text(input_stripped, 'Status', 'Priority'),
            'priority':
            parse_prompt_text(input_stripped, 'Priority', 'Component'),
            'components':
            parse_prompt_text(input_stripped, 'Component', 'Version'),
            'fixVersions':
            parse_prompt_text(input_stripped, 'Version', 'Assignee'),
            'assignee':
            parse_prompt_text(input_stripped, 'Assignee'),
        }

        # Check if required fields was entered by user
        if section['summary'] == '' or section['project'] == '' or section[
                'issuetype'] == '':
            return

        issue_kwargs = {
            'project': section['project'],
            'summary': section['summary'],
            'description': section['description'],
            'issuetype': {
                'name': section['issuetype']
            },
            'priority': {
                'name': section['priority']
            },
            'components': [{
                'name': section['components']
            }],
            'fixVersions': [{
                'name': section['fixVersions']
            }],
            'assignee': {
                'name': section['assignee']
            },
        }

        # Jira API doesn't accept empty fields for certain keys
        for key in issue_kwargs.copy().keys():
            if section[key] == '':
                issue_kwargs.pop(key)

        # Create issue and transition
        issue_key = self.jira.create_issue(**issue_kwargs)
        if section['status'] != '':
            self.jira.transition_issue(issue_key, section['status'])

        jira_server = vim.eval('g:vira_serv')
        print(f'Added {jira_server}/browse/{issue_key}')

    def add_worklog(self, issue, timeSpentSeconds, comment):
        '''
        Calculate the offset for the start time of the time tracking
        '''

        earlier = datetime.now() - datetime.timedelta(seconds=timeSpentSeconds)

        self.jira.add_worklog(issue=issue,
                              timeSpentSeconds=timeSpentSeconds,
                              comment=comment,
                              started=earlier)

    def connect(self, server):
        '''
        Connect to Jira server with supplied auth details
        '''

        self.users = set()
        self.versions = set()
        self.users_type = ''

        try:
            # Specify whether the server's TLS certificate needs to be verified
            if self.vira_servers[server].get('skip_cert_verify'):
                urllib3.disable_warnings(
                    urllib3.exceptions.InsecureRequestWarning)
                cert_verify = False
            else:
                cert_verify = True

            # Get auth for current server
            username = self.vira_servers[server].get('username')
            password_cmd = self.vira_servers[server].get('password_cmd')
            if password_cmd:
                password = run_command(password_cmd)['stdout'].strip().split(
                    '\n')[0]
            else:
                password = self.vira_servers[server]['password']
        except:
            cert_verify = True
            server = vim.eval('input("server: ")')
            vim.command('let g:vira_serv = "' + server + '"')
            username = vim.eval('input("username: "******"password: "******"' + server + '"')

            # Authorize
            self.jira = JIRA(options={
                'server': server,
                'verify': cert_verify,
            },
                             basic_auth=(username, password),
                             timeout=2,
                             async_=True,
                             max_retries=2)

            # Initial list updates
            self.users = self.get_users()
            self.get_projects()
            self.get_versions()

            vim.command('echo "Connection to jira server was successful"')
        except JIRAError as e:
            if 'CAPTCHA' in str(e):
                vim.command(
                    'echo "Could not log into jira! Check authentication details and log in from web browser to enter mandatory CAPTCHA."'
                )
            else:
                #  vim.command('echo "' + str(e) + '"')
                vim.command('let g:vira_serv = ""')
                #  raise e
        except:
            vim.command('let g:vira_serv = ""')
            vim.command(
                'echo "Could not log into jira! See the README for vira_server.json information"'
            )

    def filter_str(self, filterType):
        '''
        Build a filter string to add to a JQL query
        The string will look similar to one of these:
            AND status in ('In Progress')
            AND status in ('In Progress', 'To Do')
        '''

        if self.userconfig_filter.get(filterType, '') == '':
            return

        selection = str(
            self.userconfig_filter[filterType]).strip('[]') if type(
                self.userconfig_filter[filterType]
            ) == list else self.userconfig_filter[filterType] if type(
                self.userconfig_filter[filterType]
            ) == tuple else "'" + self.userconfig_filter[filterType] + "'"

        return str(f"{filterType} in ({selection})").replace(
            "'None'", "Null").replace("'Unassigned'", "Null").replace(
                "'currentUser'", "currentUser()").replace(
                    "'currentUser()'", "currentUser()").replace(
                        "'currentuser'", "currentUser()").replace(
                            "'currentuser()'",
                            "currentUser()").replace("'null'", "Null").replace(
                                f"text in ({selection})",
                                f"text ~ {selection}")

    def get_assign_issue(self):
        '''
        Menu to select users
        '''

        self.print_users()

    def get_assignees(self):
        '''
        Get my issues with JQL
        '''

        self.print_users()

    def get_comments(self, issue):
        '''
        Get all the comments for an issue
        '''

        # Get the issue requested
        issues = self.jira.search_issues('issue = "' + issue.key + '"',
                                         fields='summary,comment',
                                         json_result='True')

        # Loop through all of the comments
        comments = ''
        for comment in issues["issues"][0]["fields"]["comment"]["comments"]:
            comments += (f"{comment['author']['displayName']}" + ' | ',
                         f"{comment['updated'][0:10]}" + ' @ ',
                         f"{comment['updated'][11:16]}" + ' | ',
                         f"{comment['body']} + '\n'")

        return comments

    def get_components(self):
        '''
        Build a vim pop-up menu for a list of components
        '''

        for component in self.jira.project_components(
                self.userconfig_filter['project']):
            print(component.name)
        print('None')

    def get_component(self):
        '''
        Build a vim pop-up menu for a list of components
        '''

        self.get_components()

    def get_epic(self):
        self.get_epics()

    def get_epics(self):
        '''
        Get my issues with JQL
        '''
        hold = dict(self.userconfig_filter)
        project = self.userconfig_filter['project']
        self.reset_filters()
        self.userconfig_filter["issuetype"] = "Epic"
        self.userconfig_filter["project"] = project
        self.get_issues()
        print('None')
        self.userconfig_filter = hold

    def get_issue(self, issue):
        '''
        Get single issue by issue id
        '''

        return self.jira.issue(issue)

    def get_issues(self):
        '''
        Get my issues with JQL
        '''

        issues = []
        key_length = 0
        summary_length = 0
        issuetype_length = 0
        status_length = 4
        user_length = 0

        for issue in self.query_issues():
            fields = issue['fields']

            user = str(fields['assignee']['displayName']) if type(
                fields['assignee']) == dict else 'Unassigned'
            user_length = len(user) if len(user) > user_length else user_length

            key_length = len(
                issue['key']) if len(issue['key']) > key_length else key_length

            summary = fields['summary']
            summary_length = len(
                summary) if len(summary) > summary_length else summary_length

            issuetype = fields['issuetype']['name']
            issuetype_length = len(issuetype) if len(
                issuetype) > issuetype_length else issuetype_length

            status = fields['status']['name']
            status_length = len(
                status) if len(status) > status_length else status_length

            issues.append([
                issue['key'], fields['summary'], fields['issuetype']['name'],
                fields['status']['name'], user
            ])

        # Add min/max limits on summary length
        columns = vim.eval("&columns")
        min_summary_length = 25
        max_summary_length = int(
            columns) - key_length - issuetype_length - status_length - 28
        summary_length = min_summary_length if max_summary_length < min_summary_length else max_summary_length if summary_length > max_summary_length else summary_length

        for issue in issues:
            print(('{: <' + str(key_length) + '}').format(issue[0]) + " │ " +
                  ('{: <' + str(summary_length) +
                   '}').format(issue[1][:summary_length]) + "  │ " +
                  ('{: <' + str(issuetype_length) + '}').format(issue[2]) +
                  " │ " +
                  ('{: <' + str(status_length) + '}').format(issue[3]) +
                  ' │ ' + issue[4])

    def get_issuetypes(self):
        '''
        Get my issues with JQL
        '''

        for issuetype in self.jira.issue_types():
            print(issuetype)

    def get_issuetype(self):
        '''
        Get my issues with JQL
        '''

        for issuetype in self.jira.issue_types():
            print(issuetype)

    def get_priorities(self):
        '''
        Get my issues with JQL
        '''

        for priority in self.jira.priorities():
            print(priority)

    def print_projects(self):
        '''
        Build a vim pop-up menu for a list of projects
        '''

        all_projects = self.get_projects()
        batch_size = 10
        project_batches = [
            all_projects[i:i + batch_size]
            for i in range(0, len(all_projects), batch_size)
        ]

        for batch in project_batches:
            projects = self.jira.createmeta(projectKeys=','.join(batch),
                                            expand='projects')['projects']
            [print(p['key'] + ' ~ ' + p['name']) for p in projects]

    def get_projects(self):
        '''
        Build a vim pop-up menu for a list of projects
        '''

        # Project filter for version list
        self.projects = []
        for project in self.jira.projects():
            self.projects.append(str(project))
        vim.command('let s:projects = ' + str(self.projects))

        return self.projects

    def get_priority(self):
        '''
        Build a vim pop-up menu for a list of projects
        '''

        self.get_priorities()

    def get_prompt_text(self, prompt_type, comment_id=None):
        '''
        Get prompt text used for inputting text into jira
        '''

        self.prompt_type = prompt_type

        # Edit filters
        if prompt_type == 'edit_filter':
            self.prompt_text_commented = '\n# Edit all filters in JSON format'
            self.prompt_text = json.dumps(
                self.userconfig_filter,
                indent=True) + self.prompt_text_commented
            return self.prompt_text

        # Edit summary
        active_issue = vim.eval("g:vira_active_issue")
        if prompt_type == 'summary':
            self.prompt_text_commented = '\n# Edit issue summary'
            summary = self.jira.search_issues(
                'issue = "' + active_issue + '"',
                fields=','.join(['summary']),
                json_result='True')['issues'][0]['fields']['summary']
            self.prompt_text = summary + self.prompt_text_commented
            return self.prompt_text

        # Edit description
        if prompt_type == 'description':
            self.prompt_text_commented = '\n# Edit issue description'
            description = self.jira.search_issues(
                'issue = "' + active_issue + '"',
                fields=','.join(['description']),
                json_result='True')['issues'][0]['fields'].get('description')
            if description:
                description = description.replace('\r\n', '\n')
            else:
                description = ''
            self.prompt_text = description + self.prompt_text_commented
            return self.prompt_text

        self.prompt_text_commented = '''
# ---------------------------------
# Please enter text above this line
# An empty message will abort the operation.
#
# Below is a list of acceptable values for each input field.
#
# Users:'''
        for user in self.users:
            user = user.split(' ~ ')
            name = user[0]
            id = user[1]
            if self.users_type == 'accountId':
                self.prompt_text_commented += f'''
# [{name}|~accountid:{id}]'''
            else:
                self.prompt_text_commented += f'''
# [~{id}]'''

        # Add comment
        if self.prompt_type == 'add_comment':
            self.prompt_text = self.prompt_text_commented
            return self.prompt_text

        # Edit comment
        if self.prompt_type == 'edit_comment':
            self.active_comment = self.jira.comment(active_issue, comment_id)
            self.prompt_text = self.active_comment.body + self.prompt_text_commented
            return self.prompt_text

        statuses = [x.name for x in self.jira.statuses()]
        issuetypes = [x.name for x in self.jira.issue_types()]
        priorities = [x.name for x in self.jira.priorities()]
        components = [
            x.name for x in self.jira.project_components(
                self.userconfig_filter['project'])
        ] if self.userconfig_filter['project'] != '' else ''
        versions = [
            x.name for x in self.jira.project_versions(
                self.userconfig_filter['project'])
        ] if self.userconfig_filter['project'] != '' else ''
        projects = [x.key for x in self.jira.projects()]

        # Extra info for prompt_type == 'issue'
        self.prompt_text_commented += f'''
#
# Projects: {projects}
# IssueTypes: {issuetypes}
# Statuses: {statuses}
# Priorities: {priorities}
# Components in {self.userconfig_filter["project"]} Project: {components}
# Versions in {self.userconfig_filter["project"]} Project: {versions}'''

        self.prompt_text = f'''[*Summary*]
[Description]

[*Project*] {self.userconfig_filter["project"]}
[*IssueType*] {self.userconfig_newissue["issuetype"]}
[Status] {self.userconfig_newissue["status"]}
[Priority] {self.userconfig_newissue["priority"]}
[Component] {self.userconfig_newissue["component"]}
[Version] {self.userconfig_newissue["fixVersion"]}
[Assignee] {self.userconfig_newissue["assignee"]}
{self.prompt_text_commented}'''
        return self.prompt_text

    def format_date(self, date):
        time = datetime.now().strptime(date,
                                       '%Y-%m-%dT%H:%M:%S.%f%z').astimezone()
        return str(time)[0:10] + ' ' + str(time)[11:16]

    def get_report(self):
        '''
        Print a report for the given issue
        '''

        for customfield in self.jira.fields():
            if customfield['name'] == 'Epic Link':
                epicID = customfield['id']

        # Get passed issue content
        active_issue = vim.eval("g:vira_active_issue")
        issues = self.jira.search_issues(
            'issue = "' + active_issue + '"',
            fields=','.join([
                'project', 'summary', 'comment', 'component', 'description',
                'issuetype', 'priority', 'status', 'created', 'updated',
                'assignee', 'reporter', 'fixVersion', 'customfield_10106',
                'labels', epicID
            ]),
            json_result='True')
        issue = issues['issues'][0]['fields']

        # Prepare report data
        open_fold = '{{{'
        close_fold = '}}}'
        summary = issue['summary']
        story_points = str(issue.get('customfield_10106', ''))
        created = self.format_date(issue['created'])
        updated = self.format_date(issue['updated'])
        issuetype = issue['issuetype']['name']
        status = issue['status']['name']
        priority = issue['priority']['name']
        assignee = issue['assignee']['displayName'] if type(
            issue['assignee']) == dict else 'Unassigned'
        reporter = issue['reporter']['displayName']
        component = ', '.join([c['name'] for c in issue['components']])
        version = ', '.join([v['name'] for v in issue['fixVersions']])
        epics = str(issue.get(epicID))
        vim.command(f'let s:vira_epic_field = "{epicID}"')
        description = str(issue.get('description'))

        # Version percent for single version attacted
        #  if len(issue['fixVersions']) == 1 and version != '':
        #  version += ' | ' + self.version_percent(
        #  str(issue['project']['key']), version) + '%'

        comments = ''
        idx = 0
        for idx, comment in enumerate((issue['comment']['comments'])):
            comments += ''.join([
                comment['author']['displayName'] + ' @ ' +
                self.format_date(comment['updated']) + ' {{' + '{2\n' +
                comment['body'] + '\n}}}\n'
            ])
        old_count = idx - 3
        old_comment = 'Comment' if old_count == 1 else 'Comments'
        comments = ''.join(
            [str(old_count) + ' Older ' + old_comment +
             ' {{{1\n']) + comments if old_count >= 1 else comments
        comments = comments.replace('}}}', '}}}}}}', idx - 3)
        comments = comments.replace('}}}}}}', '}}}', idx - 4)

        # Find the length of the longest word [-1]
        words = [
            created, updated, issuetype, status, story_points, priority,
            component, version, assignee, reporter, epics
        ]
        wordslength = sorted(words, key=len)[-1]
        s = '─'
        dashlength = s.join([char * len(wordslength) for char in s])

        active_issue_spacing = int((16 + len(dashlength)) / 2 -
                                   len(active_issue) / 2)
        active_issue_spaces = ' '.join(
            [char * (active_issue_spacing) for char in ' '])
        active_issue_space = ' '.join([
            char * ((len(active_issue) + len(dashlength)) % 2) for char in ' '
        ])

        created_spaces = ' '.join(
            [char * (len(dashlength) - len(created)) for char in ' '])
        updated_spaces = ' '.join(
            [char * (len(dashlength) - len(updated)) for char in ' '])
        task_type_spaces = ' '.join(
            [char * (len(dashlength) - len(issuetype)) for char in ' '])
        status_spaces = ' '.join(
            [char * (len(dashlength) - len(status)) for char in ' '])
        story_points_spaces = ''.join(
            [char * (len(dashlength) - len(story_points)) for char in ' '])
        priority_spaces = ''.join(
            [char * (len(dashlength) - len(priority)) for char in ' '])
        component_spaces = ''.join(
            [char * (len(dashlength) - len(component)) for char in ' '])
        version_spaces = ''.join(
            [char * (len(dashlength) - len(version)) for char in ' '])
        assignee_spaces = ''.join(
            [char * (len(dashlength) - len(assignee)) for char in ' '])
        reporter_spaces = ''.join(
            [char * (len(dashlength) - len(reporter)) for char in ' '])
        epics_spaces = ''.join(
            [char * (len(dashlength) - len(epics)) for char in ' '])

        # Create report template and fill with data
        report = '''┌────────────────{dashlength}─┐
│{active_issue_spaces}{active_issue}{active_issue_spaces}{active_issue_space} │
├──────────────┬─{dashlength}─┤
│      Created │ {created}{created_spaces} │
│      Updated │ {updated}{updated_spaces} │
│         Type │ {issuetype}{task_type_spaces} │
│       Status │ {status}{status_spaces} │
│ Story Points │ {story_points}{story_points_spaces} │
│     Priority │ {priority}{priority_spaces} │
│    Epic Link │ {epics}{epics_spaces} │
│ Component(s) │ {component}{component_spaces} │
│   Version(s) │ {version}{version_spaces} │
│     Assignee │ {assignee}{assignee_spaces} │
│     Reporter │ {reporter}{reporter_spaces} │
└──────────────┴─{dashlength}─┘
┌──────────────┐
│    Summary   │
└──────────────┘
{summary}

┌──────────────┐
│  Description │
└──────────────┘
{description}

┌──────────────┐
│   Comments   │
└──────────────┘
{comments}'''

        self.set_report_lines(report, description, issue)

        self.prompt_text = self.report_users(report.format(**locals()))
        return self.prompt_text

    def report_users(self, report):
        '''
        Replace report accountid with names
        '''

        for user in self.users:
            user = user.split(' ~ ')
            if user[0] != "Unassigned":
                report = report.replace('accountid:',
                                        '').replace('[~' + user[1] + ']',
                                                    '[~' + user[0] + ']')

        return report

    def get_reporters(self):
        '''
        Get my issues with JQL
        '''

        self.print_users()

    def get_servers(self):
        '''
        Get list of servers
        '''

        try:
            for server in self.vira_servers.keys():
                print(server)
            print('Null')
        except:
            self.connect('')

    def get_statuses(self):
        '''
        Get my issues with JQL
        '''

        statuses = []
        for status in self.jira.statuses():
            if str(status) not in statuses:
                statuses.append(str(status))
                print(str(status))

    def get_set_status(self):
        '''
        Get my issues with JQL
        '''

        self.get_statuses()

    def get_version(self):
        '''
        Get my issues with JQL
        '''

        self.print_versions()

    def new_component(self, name, project):
        '''
        New component added to project
        '''

        self.jira.create_component(name=name,
                                   project=project,
                                   description=name)

    def new_version(self, name, project, description):
        '''
        Get my issues with JQL
        '''

        self.jira.create_version(name=name,
                                 project=project,
                                 description=description)

    def print_users(self):
        '''
        Print users
        '''

        print(self.get_current_user() + ' ~ currentUser')
        for user in self.users:
            print(user)
        print('Unassigned')

    def get_users(self):
        '''
        Get my issues with JQL
        '''

        query = 'ORDER BY updated DESC'
        issues = self.jira.search_issues(query,
                                         fields='assignee, reporter',
                                         json_result='True',
                                         maxResults=-1)

        # Determine cloud/server jira
        self.users_type = 'accountId' if issues['issues'][0]['fields'][
            'reporter'].get('accountId') else 'name'

        for issue in issues['issues']:
            user = str(issue['fields']['reporter']['displayName']
                       ) + ' ~ ' + issue['fields']['reporter'][self.users_type]
            self.users.add(user)
            if type(issue['fields']['assignee']) == dict:
                user = str(
                    issue['fields']['assignee']['displayName']
                ) + ' ~ ' + issue['fields']['assignee'][self.users_type]
            self.users.add(user)

        return sorted(self.users)

    def get_current_user(self):
        query = 'reporter = currentUser() or assignee = currentUser()'
        issues = self.jira.search_issues(query,
                                         fields='assignee, reporter',
                                         json_result='True',
                                         maxResults=-1)

        issue = issues['issues'][0]['fields']
        return str(issue['assignee'][self.users_type] if type(
            issue['assignee']) == dict else issue['reporter'][self.users_type]
                   if type(issue['reporter']) == dict else 'Unassigned')

    def print_versions(self):
        '''
        Print version list with project filters
        '''
        try:
            versions = sorted(self.versions)
            wordslength = sorted(versions, key=len)[-1]
            s = ' '
            dashlength = s.join([char * len(wordslength) for char in s])
            for version in versions:
                print(
                    version.split('|')[0] + ''.join([
                        char * (len(dashlength) - len(version)) for char in ' '
                    ]) + '   ' + version.split('|')[1] + ' ' +
                    version.split('|')[2])
        except:
            pass
        print('None')

    def version_percent(self, project, fixVersion):
        project = str(project)
        fixVersion = str(fixVersion)
        if str(project) != '[]' and str(project) != '' and str(
                fixVersion) != '[]' and str(fixVersion) != '':
            query = 'fixVersion = ' + fixVersion + ' AND project = "' + project + '"'
            issues = self.jira.search_issues(query,
                                             fields='fixVersion',
                                             json_result='True',
                                             maxResults=1)

            try:
                issue = issues['issues'][0]['fields']['fixVersions'][0]
                idx = issue['id']

                total = self.jira.version_count_related_issues(
                    idx)['issuesFixedCount']
                pending = self.jira.version_count_unresolved_issues(idx)
                fixed = total - pending
                percent = str(round(fixed / total *
                                    100, 1)) if total != 0 else 1
                space = ''.join([char * (5 - len(percent)) for char in ' '])

                name = fixVersion
                try:
                    description = issue['description']
                except:
                    description = 'None'
                    pass
            except:
                total = 0
                pending = 0
                fixed = total - pending
                percent = "0"
                space = ''.join([char * (5 - len(percent)) for char in ' '])
                name = fixVersion
                description = ''
                pass

            version = str(
                str(name) + ' ~ ' + str(description) + '|' + str(fixed) + '/' +
                str(total) + space + '|' + str(percent) + '%')

            self.versions_hide = vim.eval('g:vira_version_hide')
            if fixed != total or total == 0 or not int(
                    self.versions_hide) == 1:
                self.versions.add(
                    str(project) + ' ~ ' + str(version.replace('\'', '')))

        else:
            percent = 0

        return percent

    def get_versions(self):
        '''
        Build a vim pop-up menu for a list of versions with project filters
        '''

        # Loop through each project and all versions within
        try:
            for v in reversed(
                    self.jira.project_versions(vim.eval('s:projects[0]'))):
                vim.command('let s:versions = add(s:versions,\"' + str(v) +
                            '\")')
        except:
            vim.command('let s:versions = []')

    def load_project_config(self, repo):
        '''
        Load project configuration for the current git repo
        The current repo can either be determined by current files path
        or by the user setting g:vira_repo (part of :ViraLoadProject)

        For example, an entry in projects.yaml may be:

        vira:
          server: https://jira.tgall.ca
          project_name: VIRA
        '''

        # Only proceed if projects file parsed successfully
        if not getattr(self, 'vira_projects', None):
            return

        # If current repo/folder doesn't exist, use __default__ project config if it exists
        if repo == '':
            repo = run_command(
                'git rev-parse --show-toplevel')['stdout'].strip()
            if not self.vira_projects.get(repo):
                repo = repo.split('/')[-1]
            if not self.vira_projects.get(repo):
                repo = run_command('pwd')['stdout'].strip()
            if not self.vira_projects.get(repo):
                repo = repo.split('/')[-1]
            if not self.vira_projects.get(repo):
                repo = '__default__'
            if not self.vira_projects.get('__default__'):
                return

        # Set server
        server = self.vira_projects.get(repo, {}).get('server')
        if server:
            vim.command(f'let g:vira_serv = "{server}"')

        # Set user-defined filters for current project
        for key in self.userconfig_filter.keys():
            value = self.vira_projects.get(repo, {}).get('filter', {}).get(key)
            if value:
                self.userconfig_filter[key] = value

        # Set user-defined new-issue defaults for current project
        for key in self.userconfig_newissue.keys():
            value = self.vira_projects.get(repo, {}).get('newissue',
                                                         {}).get(key)
            if value:
                self.userconfig_newissue[key] = value

        # Set user-defined issue sort options
        sort_order = self.vira_projects.get(repo,
                                            {}).get('issuesort',
                                                    'updated DESC')
        self.userconfig_issuesort = ', '.join(sort_order) if type(
            sort_order) == list else sort_order

    def query_issues(self):
        '''
        Query issues based on current filters
        '''

        q = []
        for filterType in self.userconfig_filter.keys():
            filter_str = self.filter_str(filterType)
            if filter_str:
                q.append(filter_str)

        query = ' AND '.join(q) + ' ORDER BY ' + self.userconfig_issuesort
        issues = self.jira.search_issues(
            query,
            fields='summary,comment,status,statusCategory,issuetype,assignee',
            json_result='True',
            maxResults=vim.eval('g:vira_issue_limit'))

        return issues['issues']

    def reset_filters(self):
        '''
        Reset filters to their default values
        '''

        self.userconfig_filter = dict(self.userconfig_filter_default)

    def set_report_lines(self, report, description, issue):
        '''
        Create dictionary for vira report that shows relationship
        between line numbers and fields to be edited
        '''

        writable_fields = {
            'Assignee': 'ViraSetAssignee',
            'Component': 'ViraSetComponent',
            'Priority': 'ViraSetPriority',
            'Epic Link': 'ViraSetEpic',
            'Status': 'ViraSetStatus',
            'Type': 'ViraSetType',
            'Version': 'ViraSetVersion',
        }

        self.report_lines = {}

        for idx, line in enumerate(report.split('\n')):
            for field, command in writable_fields.items():
                if field in line:
                    self.report_lines[idx + 1] = command
                    continue

        for x in range(16, 21):
            self.report_lines[x] = 'ViraEditSummary'

        description_len = description.count('\n') + 3
        for x in range(21, 23 + description_len):
            self.report_lines[x] = 'ViraEditDescription'

        offset = 2 if len(issue['comment']['comments']) > 4 else 1
        comment_line = 25 + description_len + offset
        for comment in issue['comment']['comments']:
            comment_len = comment['body'].count('\n') + 3
            for x in range(comment_line, comment_line + comment_len):
                self.report_lines[x] = 'ViraEditComment ' + comment['id']
            comment_line = comment_line + comment_len

    def set_prompt_text(self):
        '''
        Take the user prompt text and perform an action
        Usually, this involves writing to the jira server
        '''

        # User input
        issue = vim.eval('g:vira_active_issue')
        userinput = vim.eval('g:vira_input_text')
        input_stripped = userinput.replace(self.prompt_text_commented.strip(),
                                           '').strip()

        # Check if anything was actually entered by user
        if input_stripped == '' or userinput.strip() == self.prompt_text.strip(
        ):
            print("No vira actions performed")
            return

        if self.prompt_type == 'edit_filter':
            self.userconfig_filter = json.loads(input_stripped)
        elif self.prompt_type == 'add_comment':
            self.jira.add_comment(issue, input_stripped)
        elif self.prompt_type == 'edit_comment':
            self.active_comment.update(body=input_stripped)
        elif self.prompt_type == 'summary':
            self.jira.issue(issue).update(summary=input_stripped)
        elif self.prompt_type == 'description':
            self.jira.issue(issue).update(description=input_stripped)
        elif self.prompt_type == 'issue':
            self.create_issue(input_stripped)

    def versions_hide(self, state):
        '''
        Display and hide complete versions
        '''

        if state is True or 1 or 'ture' or 'True':
            self.version_hide = True
        elif state is False or 0 or 'false' or 'False':
            self.version_hide = False
        else:
            self.version_hide = not self.version_hide
Пример #19
0
def main():

    statusDict = {
        'integration': {
            'transitionId': 131,
            'status': 'MERGED'
        },
        'uat': {
            'transitionId': 151,
            'status': 'APPROVED'
        }
    }

    # Determine which product is to be deployed
    product = input(
        'Is this a \"VICE\", \"Workflow\", or \"Dispatch\" deployment?\n')

    while product.lower() not in [
            'vice', 'dispatch', 'dis', 'vc', 'workflow', 'wrk'
    ]:
        try:
            product = input('Invalid selection. Try again...\n')
        except:
            print('Ya blew it...')
            sys.exit(1)

    # Determine to which environment changes are to be deployed
    environment = input('Is this an \"integration\" or \"UAT\" deployment?\n')

    while environment.lower() not in ['integration', 'uat']:
        try:
            environment = input('Invalid selection. Try again...\n')
        except:
            print('Ya blew it...')
            sys.exit(1)

    # Assign appropriate values to variables based on selections
    if product.lower() in ['vice', 'vc']:
        project = 'VC'
    elif product.lower() in ['dispatch', 'dis']:
        project = 'DIS'
    elif product.lower() in ['workflow', 'wrk']:
        project = 'WRK'
    # project = 'SBX'

    if environment.lower() == 'integration':
        statusInfo = statusDict['integration']
    else:
        statusInfo = statusDict['uat']

    # Authenticate
    options = {'server': 'https://snapsheettech.atlassian.net/'}
    jira = JIRA(options, basic_auth=('EMAIL HERE', 'API KEY HERE'))

    # Create new version in the appropriate project. Use existing version if matching version already exists.
    version = input('Please provide a name for the Fix Version: ')
    try:
        newVersion = jira.create_version(version, project)
    except:
        projectVersions = jira.project_versions(project)
        for i in range(len(projectVersions)):
            if projectVersions[i].name == version:
                newVersion = projectVersions[i]
        pass

    # Transition issues and add new Fix Version
    print('\nWorking... This may take some time.\n')

    # Issue queries are broken up into 100 issue chunks due to limitations of the Jira API
    block_num = 0
    block_size = 100

    while True:
        start_idx = block_num * block_size
        issues = jira.search_issues(
            'project = %s AND status = %s order by created DESC' %
            (project, statusInfo['status']),
            startAt=start_idx,
            maxResults=block_size)
        if len(issues) == 0:
            break
        block_num += 1

        for issue in issues:
            fixVersions = []
            for version in issue.fields.fixVersions:
                fixVersions.append({'name': version.name})
            fixVersions.append({'name': newVersion.name})
            jira.transition_issue(issue.id,
                                  statusInfo['transitionId'],
                                  fields={'fixVersions': fixVersions},
                                  comment='Issue transitioned via automation.')

    print('All done.\n')
Пример #20
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
Пример #21
0
    def create_bug_issue(self, channel, summary, description, component, version, labels,
                         user=JIRA_USER, passwd=JIRA_PASS, project=JIRA_PROJECT):
        """
        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
        """
        if user and passwd and project:
            try:
                jira = JIRA(server='https://issues.voltdb.com/', basic_auth=(user, passwd))
            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 bug with same summary
        existing = jira.search_issues('summary ~ \'%s\'' % summary)
        if len(existing) > 0:
            # Already reported
            self.logger.info('OLD: Already reported issue with summary "' + summary + '"')
            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'}

        new_issue = jira.create_issue(fields=issue_dict)
        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)
Пример #22
0
class JiraClient:
    def __init__(self, email, token, server=SERVER):
        self.client = JIRA(server=server,
                           basic_auth=(email, token),
                           max_retries=MAX_RETRIES,
                           timeout=4)

    def get_issues(self, start_at=0, query='', limit=ISSUES_COUNT):
        return self.client.search_issues(
            query,
            fields='key, summary, timetracking, status, assignee',
            startAt=start_at,
            maxResults=limit)

    def log_work(self,
                 issue,
                 time_spent,
                 start_date,
                 comment,
                 adjust_estimate=None,
                 new_estimate=None,
                 reduce_by=None):
        self.client.add_worklog(issue=issue,
                                timeSpent=time_spent,
                                adjustEstimate=adjust_estimate,
                                newEstimate=new_estimate,
                                reduceBy=reduce_by,
                                started=start_date,
                                comment=comment)

    def get_possible_resolutions(self):
        resolutions = self.client.resolutions()
        possible_resolutions = [resolution.name for resolution in resolutions]

        return possible_resolutions

    def get_possible_versions(self, issue):
        all_projects = self.client.projects()
        current_project_key = issue.key.split('-')[0]

        for id, project in enumerate(all_projects):
            if project.key == current_project_key:
                current_project_id = id

        versions = self.client.project_versions(
            all_projects[current_project_id])
        possible_versions = [version.name for version in versions]

        return possible_versions

    @staticmethod
    def get_remaining_estimate(issue):
        try:
            existing_estimate = issue.fields.timetracking.raw[
                'remainingEstimate']
        except (AttributeError, TypeError, KeyError):
            existing_estimate = "0m"
        return existing_estimate

    @staticmethod
    def get_original_estimate(issue):
        try:
            original_estimate = issue.fields.timetracking.originalEstimate
        except JIRAError as e:
            return e.text
        except (AttributeError, TypeError):
            return "You should establish estimate first"
        return original_estimate

    def issue(self, key):
        return self.client.issue(key)
Пример #23
0
    def create_bug_issue(self,
                         channel,
                         summary,
                         description,
                         component,
                         version,
                         labels,
                         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
        """
        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:
            # Already reported
            self.logger.info('Found open issue(s) for "' + test_case + '" ' +
                             ' '.join([k.key for k in existing]))
            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)
            #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)
        else:
            new_issue = None

        return new_issue
Пример #24
0
    def report_issue(self, build):
        try:
            jira = JIRA(server='https://issues.voltdb.com/',
                        basic_auth=(JIRA_USER, JIRA_PASS),
                        options=dict(verify=False))
        except:
            logging.exception('Could not connect to Jira')
            return

        build_report_url = self.jhost + '/job/' + job + '/' + str(
            build) + '/api/python'
        build_report = eval(self.read_url(build_report_url))
        build_url = build_report.get('url')
        build_result = build_report.get('result')

        if build_result == 'SUCCESS':  # only generate Jira issue if the test fails
            print 'No new issue created. Build ' + str(
                build) + 'resulted in: ' + build_result
            return

        summary_url = self.jhost + '/job/' + job + '/' + str(
            build) + '/artifact/summary.out'
        summary_report = self.read_url(summary_url)

        pframe_split = summary_report.split('Problematic frame:')
        pframe_split = pframe_split[1].split('C')
        pframe_split = pframe_split[1].split(']')
        pframe_split = pframe_split[1].split('#')
        pframe = pframe_split[0].strip()

        summary = job + ':' + str(build) + ' - ' + pframe
        # search_issues gets a parsing error on (), so escape it.
        existing = jira.search_issues('summary ~ \'%s\'' %
                                      summary.replace('()', '\\\\(\\\\)', 10))
        if len(existing) > 0:
            print 'No new Jira issue created. Build ' + str(
                build) + ' has already been reported.'
            return 'Already reported'

        old_issue = ''
        existing = jira.search_issues(
            'summary ~ \'%s\'' %
            pframe_split[0].strip().replace('()', '\\\\(\\\\)', 10))
        for issue in existing:
            if str(issue.fields.status
                   ) != 'Closed' and u'grammar-gen' in issue.fields.labels:
                old_issue = issue

        build_artifacts = build_report.get('artifacts')[0]
        pid_fileName = build_artifacts['fileName']
        pid_url = build_url + 'artifact/' + pid_fileName

        query_split = summary_report.split(
            '(or it was never started??), after SQL statement:')
        crash_query = query_split[1]

        hash_split = summary_report.split('#', 1)
        hash_split = hash_split[1].split(
            '# See problematic frame for where to report the bug.')
        sigsegv_message = hash_split[
            0] + '# See problematic frame for where to report the bug.\n#'

        description = job + ' build ' + str(build) + ' : ' + str(build_result) + '\n' \
            + 'Jenkins build: ' + build_url + ' \n \n' \
            + 'DDL: ' + 'https://github.com/VoltDB/voltdb/blob/master/tests/sqlgrammar/DDL.sql' + ' \n \n' \
            + 'hs_err_pid: ' + pid_url + ' \n \n' \
            + 'SIGSEGV Message: \n' + '#' + sigsegv_message + ' \n \n' \
            + 'Query that Caused the Crash: ' + crash_query
        description = description.replace('#', '\#')

        labels = ['grammar-gen']

        component = 'Core'
        components = jira.project_components(JIRA_PROJECT)
        jira_component = {}
        for c in components:
            if c.name == component:
                jira_component = {'name': c.name, 'id': c.id}
                break

        current_version_raw = str(
            self.read_url(
                'https://raw.githubusercontent.com/VoltDB/voltdb/master/version.txt'
            ))
        current_version_float = float(current_version_raw)
        current_version = 'V' + current_version_raw
        current_version = current_version.strip()
        next_version = current_version_float + .1
        next_version = str(next_version)
        next_version = 'V' + next_version
        next_version = next_version[:4]

        jira_versions = jira.project_versions(JIRA_PROJECT)
        this_version = {}
        new_version = {}

        for v in jira_versions:
            if str(v.name) == current_version:
                this_version = {'name': v.name, 'id': v.id}
            if str(v.name) == next_version:
                new_version = {'name': v.name, 'id': v.id}

        issue_dict = {
            'project': JIRA_PROJECT,
            'summary': summary,
            'description': description,
            'issuetype': {
                'name': 'Bug'
            },
            'priority': {
                'name': 'Blocker'
            },
            'labels': labels,
            'customfield_10430': {
                'value': 'CORE team'
            },
            'components': [jira_component]
        }

        if new_version:
            issue_dict['versions'] = [new_version]
            issue_dict['fixVersions'] = [new_version]

        elif this_version:
            issue_dict['versions'] = [this_version]
            issue_dict['fixVersions'] = [this_version]

        if old_issue:
            new_comment = jira.add_comment(old_issue, description)
            print 'JIRA-action: New comment on issue: ' + str(
                old_issue) + ' created for failure on build ' + str(build)
        else:
            new_issue = jira.create_issue(fields=issue_dict)
            print 'JIRA-action: New issue ' + new_issue.key + ' created for failure on build ' + str(
                build)
Пример #25
0
class JiraProject:
    """
    Client in charge to retrieve all issues and comments
    for a given JIRA project
    """

    def __init__(self, jira_url, project_key):
        self.jira_url = jira_url
        self.jira_client = JIRA(
            options={'server': self.jira_url, 'verify': False},
            validate=False)
        self.project_key = project_key

    def get_comments(self, issue):
        return self.jira_client.issue(issue.key, expand='comments')

    def get_project_versions(self):
        return self.jira_client.project_versions(self.project_key)

    @staticmethod
    def get_attachments(issue):
        try:
            return issue.fields.attachment
        except AttributeError:
            return None

    @staticmethod
    def get_assignee(issue):
        try:
            return issue.fields.assignee.name
        except AttributeError:
            return None

    @staticmethod
    def get_creation_datetime(issue):
        return issue.fields.created

    @staticmethod
    def get_fix_version(issue):
        try:
            fix_versions = issue.fields.fixVersions

            if len(fix_versions) > 0:
                return fix_versions[0].name
            else:
                return None
        except AttributeError:
            return None

    @staticmethod
    def get_priority(issue):
        try:
            return issue.fields.priority.name
        except AttributeError:
            return None

    @staticmethod
    def get_resolution(issue):
        if issue.fields.resolution is not None:
            return issue.fields.resolution.name
        else:
            return None

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

    @staticmethod
    def get_type(issue):
        return issue.fields.issuetype.name

    @staticmethod
    def is_closed(issue):
        return issue.fields.resolution is not None

    def get_issues(self):
        start_index = 0
        max_nb_results = 100

        result = []

        # while start_index < max_nb_results:
        while True:
            issues = self.jira_client.search_issues(
                'project=' + self.project_key,
                startAt=start_index,
                maxResults=max_nb_results)

            result.extend(issues)

            if len(issues) == 0 or len(issues) < max_nb_results:
                break
            else:
                start_index += max_nb_results

        return sorted(result, key=lambda issue: int(
            issue.key[issue.key.index('-') + 1:]))

    def get_attachment_information(self):
        start_index = 0
        max_nb_results = 100

        result = []

        # while start_index < max_nb_results \
        while True:
            issues = self.jira_client.search_issues(
                'project=' + self.project_key,
                fields='attachment',
                startAt=start_index,
                maxResults=max_nb_results)

            for issue in issues:
                a = self.get_attachments(issue)
                if a is not None and len(a) > 0:
                    [result.append((issue.key, v.id)) for v in a]

            if len(issues) == 0 or len(issues) < max_nb_results:
                break
            else:
                start_index += max_nb_results

        return result

    def get_attachment(self, attachment_id):
        return self.jira_client.attachment(attachment_id)
Пример #26
0
class ViraAPI():
    '''
    This class gets imported by __init__.py
    '''
    def __init__(self):
        '''
        Initialize vira
        '''

        # Load user-defined config files
        file_servers = vim.eval('g:vira_config_file_servers')
        file_projects = vim.eval('g:vira_config_file_projects')
        try:
            self.vira_servers = load_config(file_servers)
            self.vira_projects = load_config(file_projects)
        except:
            print(f'Could not load {file_servers} or {file_projects}')

        self.userconfig_filter_default = {
            'assignee': '',
            'component': '',
            'fixVersion': '',
            'issuetype': '',
            'priority': '',
            'project': '',
            'reporter': '',
            'status': '',
            'statusCategory': ['To Do', 'In Progress'],
            'text': ''
        }
        self.reset_filters()

        self.userconfig_newissue = {
            'assignee': '',
            'component': '',
            'fixVersion': '',
            'issuetype': 'Bug',
            'priority': '',
            'status': '',
        }

    def create_issue(self, input_stripped):
        '''
        Create new issue in jira
        '''

        section = {
            'summary':
            parse_prompt_text(input_stripped, '*Summary*', 'Description'),
            'description':
            parse_prompt_text(input_stripped, 'Description', '*Project*'),
            'project':
            parse_prompt_text(input_stripped, '*Project*', '*IssueType*'),
            'issuetype':
            parse_prompt_text(input_stripped, '*IssueType*', 'Status'),
            'status':
            parse_prompt_text(input_stripped, 'Status', 'Priority'),
            'priority':
            parse_prompt_text(input_stripped, 'Priority', 'Component'),
            'components':
            parse_prompt_text(input_stripped, 'Component', 'Version'),
            'fixVersions':
            parse_prompt_text(input_stripped, 'Version', 'Assignee'),
            'assignee':
            parse_prompt_text(input_stripped, 'Assignee'),
        }

        # Check if required fields was entered by user
        if section['summary'] == '' or section['project'] == '' or section[
                'issuetype'] == '':
            return

        issue_kwargs = {
            'project': section['project'],
            'summary': section['summary'],
            'description': section['description'],
            'issuetype': {
                'name': section['issuetype']
            },
            'priority': {
                'name': section['priority']
            },
            'components': [{
                'name': section['components']
            }],
            'fixVersions': [{
                'name': section['fixVersions']
            }],
            'assignee': {
                'name': section['assignee']
            },
        }

        # Jira API doesn't accept empty fields for certain keys
        for key in issue_kwargs.copy().keys():
            if section[key] == '':
                issue_kwargs.pop(key)

        # Create issue and transition
        issue_key = self.jira.create_issue(**issue_kwargs)
        if section['status'] != '':
            self.jira.transition_issue(issue_key, section['status'])

        jira_server = vim.eval('g:vira_serv')
        print(f'Added {jira_server}/browse/{issue_key}')

    def add_worklog(self, issue, timeSpentSeconds, comment):
        '''
        Calculate the offset for the start time of the time tracking
        '''

        earlier = datetime.datetime.now() - datetime.timedelta(
            seconds=timeSpentSeconds)

        self.jira.add_worklog(issue=issue,
                              timeSpentSeconds=timeSpentSeconds,
                              comment=comment,
                              started=earlier)

    def connect(self, server):
        '''
        Connect to Jira server with supplied auth details
        '''

        # Specify whether the server's TLS certificate needs to be verified
        if self.vira_servers[server].get('skip_cert_verify'):
            urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
            cert_verify = False
        else:
            cert_verify = True

        # Get auth for current server
        username = self.vira_servers[server].get('username')
        password_cmd = self.vira_servers[server].get('password_cmd')
        if password_cmd:
            password = run_command(password_cmd)['stdout'].strip()
        else:
            password = self.vira_servers[server]['password']

        # Connect to jira server
        try:
            self.jira = JIRA(options={
                'server': server,
                'verify': cert_verify,
            },
                             basic_auth=(username, password),
                             timeout=5)
            vim.command('echo "Connection to jira server was successful"')
        except JIRAError as e:
            if 'CAPTCHA' in str(e):
                vim.command(
                    'echo "Could not log into jira! Check authentication details and log in from web browser to enter mandatory CAPTCHA."'
                )
            else:
                raise e

    def filter_str(self, filterType):
        '''
        Build a filter string to add to a JQL query
        The string will look similar to one of these:
            AND status in ('In Progress')
            AND status in ('In Progress', 'To Do')
        '''

        if self.userconfig_filter.get(filterType, '') == '':
            return

        selection = str(
            self.userconfig_filter[filterType]).strip('[]') if type(
                self.userconfig_filter[filterType]
            ) == list else self.userconfig_filter[filterType] if type(
                self.userconfig_filter[filterType]
            ) == tuple else "'" + self.userconfig_filter[filterType] + "'"

        return str(f"{filterType} in ({selection})").replace(
            "'null'", "Null").replace("'Unassigned'",
                                      "Null").replace(f"text in ({selection})",
                                                      f"text ~ {selection}")

    def get_assign_issue(self):
        '''
        Menu to select users
        '''

        self.get_users()

    def get_assignees(self):
        '''
        Get my issues with JQL
        '''

        self.get_users()

    def get_comments(self, issue):
        '''
        Get all the comments for an issue
        '''

        # Get the issue requested
        issues = self.jira.search_issues('issue = "' + issue.key + '"',
                                         fields='summary,comment',
                                         json_result='True')

        # Loop through all of the comments
        comments = ''
        for comment in issues["issues"][0]["fields"]["comment"]["comments"]:
            comments += (f"{comment['author']['displayName']}" + ' | ',
                         f"{comment['updated'][0:10]}" + ' @ ',
                         f"{comment['updated'][11:16]}" + ' | ',
                         f"{comment['body']} + '\n'")

        return comments

    def get_components(self):
        '''
        Build a vim popup menu for a list of components
        '''

        for component in self.jira.project_components(
                self.userconfig_filter['project']):
            print(component.name)

    def get_component(self):
        '''
        Build a vim popup menu for a list of components
        '''

        self.get_components()

    def get_epics(self):
        '''
        Get my issues with JQL
        '''

        for issue in self.query_issues(issuetypes="Epic"):
            print(issue["key"] + '  -  ' + issue["fields"]['summary'])

    def get_issue(self, issue):
        '''
        Get single issue by isuue id
        '''

        return self.jira.issue(issue)

    def get_issues(self):
        '''
        Get my issues with JQL
        '''

        issues = []
        key_length = 0
        summary_length = 0
        issuetype_length = 0
        status_length = 4
        user_length = 0

        for issue in self.query_issues():
            fields = issue['fields']

            user = str(fields['assignee']['displayName']) if type(
                fields['assignee']) == dict else 'Unassigned'
            user_length = len(user) if len(user) > user_length else user_length

            key_length = len(
                issue['key']) if len(issue['key']) > key_length else key_length

            summary = fields['summary']
            summary_length = len(
                summary) if len(summary) > summary_length else summary_length

            issuetype = fields['issuetype']['name']
            issuetype_length = len(issuetype) if len(
                issuetype) > issuetype_length else issuetype_length

            status = fields['status']['name']
            status_length = len(
                status) if len(status) > status_length else status_length

            issues.append([
                issue['key'], fields['summary'], fields['issuetype']['name'],
                fields['status']['name'], user
            ])

        # Add min/max limits on summary length
        columns = vim.eval("&columns")
        min_summary_length = 25
        max_summary_length = int(
            columns) - key_length - issuetype_length - status_length - 28
        summary_length = min_summary_length if max_summary_length < min_summary_length else max_summary_length if summary_length > max_summary_length else summary_length

        for issue in issues:
            print(('{: <' + str(key_length) + '}').format(issue[0]) + " │ " +
                  ('{: <' + str(summary_length) +
                   '}').format(issue[1][:summary_length]) + "  │ " +
                  ('{: <' + str(issuetype_length) + '}').format(issue[2]) +
                  " │ " +
                  ('{: <' + str(status_length) + '}').format(issue[3]) +
                  ' │ ' + issue[4])

    def get_issuetypes(self):
        '''
        Get my issues with JQL
        '''

        for issuetype in self.jira.issue_types():
            print(issuetype)

    def get_issuetype(self):
        '''
        Get my issues with JQL
        '''

        for issuetype in self.jira.issue_types():
            print(issuetype)

    def get_priorities(self):
        '''
        Get my issues with JQL
        '''

        for priority in self.jira.priorities():
            print(priority)

    def get_projects(self):
        '''
        Build a vim popup menu for a list of projects
        '''

        for project in self.jira.projects():
            print(project)

    def get_priority(self):
        '''
        Build a vim popup menu for a list of projects
        '''

        self.get_priorities()

    def get_prompt_text(self, prompt_type, comment_id=None):
        '''
        Get prompt text used for inputting text into jira
        '''

        # Edit summary
        self.prompt_type = prompt_type
        active_issue = vim.eval("g:vira_active_issue")
        if prompt_type == 'summary':
            self.prompt_text_commented = '\n# Edit issue summary'
            summary = self.jira.search_issues(
                'issue = "' + active_issue + '"',
                fields=','.join(['summary']),
                json_result='True')['issues'][0]['fields']['summary']
            return summary + self.prompt_text_commented

        # Edit description
        if prompt_type == 'description':
            self.prompt_text_commented = '\n# Edit issue description'
            description = self.jira.search_issues(
                'issue = "' + active_issue + '"',
                fields=','.join(['description']),
                json_result='True')['issues'][0]['fields'].get('description')
            if description:
                description = description.replace('\r\n', '\n')
            else:
                description = ''
            return description + self.prompt_text_commented

        # Prepare dynamic variables for prompt text
        query = 'ORDER BY updated DESC'
        issues = self.jira.search_issues(query,
                                         fields='assignee, reporter',
                                         json_result='True',
                                         maxResults=-1)

        # Determine cloud/server jira
        id = 'accountId' if issues['issues'][0]['fields']['reporter'].get(
            'accountId') else 'name'
        users = set()
        for issue in issues['issues']:
            user = str(issue['fields']['reporter']['displayName']
                       ) + ' ~ ' + issue['fields']['reporter'][id]
            users.add(user)
            if type(issue['fields']['assignee']) == dict:
                user = str(issue['fields']['assignee']['displayName']
                           ) + ' ~ ' + issue['fields']['assignee'][id]
            users.add(user)

        self.prompt_text_commented = f'''
# ---------------------------------
# Please enter text above this line
# An empty message will abort the operation.
#
# Below is a list of acceptable values for each input field.
# Users: {users}
'''
        # Add comment
        if self.prompt_type == 'add_comment':
            return self.prompt_text_commented

        # Edit comment
        if self.prompt_type == 'edit_comment':
            self.active_comment = self.jira.comment(active_issue, comment_id)
            return self.active_comment.body + self.prompt_text_commented

        statuses = [x.name for x in self.jira.statuses()]
        issuetypes = [x.name for x in self.jira.issue_types()]
        priorities = [x.name for x in self.jira.priorities()]
        components = [
            x.name for x in self.jira.project_components(
                self.userconfig_filter['project'])
        ] if self.userconfig_filter['project'] != '' else ''
        versions = [
            x.name for x in self.jira.project_versions(
                self.userconfig_filter['project'])
        ] if self.userconfig_filter['project'] != '' else ''
        projects = [x.key for x in self.jira.projects()]

        # Extra info for prompt_type == 'issue'
        self.prompt_text_commented += f'''# Projects: {projects}
# IssueTypes: {issuetypes}
# Statuses: {statuses}
# Priorities: {priorities}
# Components in {self.userconfig_filter["project"]} Project: {components}
# Versions in {self.userconfig_filter["project"]} Project: {versions}
'''
        return f'''[*Summary*]
[Description]

[*Project*] {self.userconfig_filter["project"]}
[*IssueType*] {self.userconfig_newissue["issuetype"]}
[Status] {self.userconfig_newissue["status"]}
[Priority] {self.userconfig_newissue["priority"]}
[Component] {self.userconfig_newissue["component"]}
[Version] {self.userconfig_newissue["fixVersion"]}
[Assignee] {self.userconfig_newissue["assignee"]}
{self.prompt_text_commented}'''

    def get_report(self):
        '''
        Print a report for the given issue
        '''

        # Get passed issue content
        active_issue = vim.eval("g:vira_active_issue")
        issues = self.jira.search_issues(
            'issue = "' + active_issue + '"',
            #  fields='*',
            fields=','.join([
                'summary', 'comment', 'component', 'description', 'issuetype',
                'priority', 'status', 'created', 'updated', 'assignee',
                'reporter', 'fixVersion', 'customfield_10106'
            ]),
            json_result='True')
        issue = issues['issues'][0]['fields']

        # Prepare report data
        open_fold = '{{{'
        close_fold = '}}}'
        summary = issue['summary']
        story_points = str(issue.get('customfield_10106', ''))
        created = issue['created'][0:10] + ' ' + issues['issues'][0]['fields'][
            'created'][11:16]
        updated = issue['updated'][0:10] + ' ' + issues['issues'][0]['fields'][
            'updated'][11:16]
        issuetype = issue['issuetype']['name']
        status = issue['status']['name']
        priority = issue['priority']['name']
        assignee = issue['assignee']['displayName'] if type(
            issue['assignee']) == dict else 'Unassigned'
        reporter = issue['reporter']['displayName']
        component = ', '.join([c['name'] for c in issue['components']])
        version = ', '.join([v['name'] for v in issue['fixVersions']])
        description = str(issue.get('description'))

        comments = ''
        idx = 0
        for idx, comment in enumerate((issue['comment']['comments'])):
            comments += ''.join([
                comment['author']['displayName'] + ' @ ' +
                #  comment['body'] + '\n}}}\n'
                comment['updated'][0:10] + ' ' + comment['updated'][11:16] +
                ' {{{2\n' + comment['body'] + '\n}}}\n'
            ])
        comments = ''.join(['Old Comments {{{1\n'
                            ]) + comments if idx > 3 else comments
        comments = comments.replace('}}}', '}}}}}}', idx - 3)
        comments = comments.replace('}}}}}}', '}}}', idx - 4)

        # Find the length of the longest word [-1]
        words = [
            created, updated, issuetype, status, story_points, priority,
            component, version, assignee, reporter
        ]
        wordslength = sorted(words, key=len)[-1]
        s = '─'
        dashlength = s.join([char * len(wordslength) for char in s])

        active_issue_spacing = int((16 + len(dashlength)) / 2 -
                                   len(active_issue) / 2)
        active_issue_spaces = ' '.join(
            [char * (active_issue_spacing) for char in ' '])
        active_issue_space = ' '.join(
            [char * (len(active_issue) % 2) for char in ' '])

        created_spaces = ' '.join(
            [char * (len(dashlength) - len(created)) for char in ' '])
        updated_spaces = ' '.join(
            [char * (len(dashlength) - len(updated)) for char in ' '])
        task_type_spaces = ' '.join(
            [char * (len(dashlength) - len(issuetype)) for char in ' '])
        status_spaces = ' '.join(
            [char * (len(dashlength) - len(status)) for char in ' '])
        story_points_spaces = ''.join(
            [char * (len(dashlength) - len(story_points)) for char in ' '])
        priority_spaces = ''.join(
            [char * (len(dashlength) - len(priority)) for char in ' '])
        component_spaces = ''.join(
            [char * (len(dashlength) - len(component)) for char in ' '])
        version_spaces = ''.join(
            [char * (len(dashlength) - len(version)) for char in ' '])
        assignee_spaces = ''.join(
            [char * (len(dashlength) - len(assignee)) for char in ' '])
        reporter_spaces = ''.join(
            [char * (len(dashlength) - len(reporter)) for char in ' '])

        # Create report template and fill with data
        report = '''┌────────────────{dashlength}─┐
│{active_issue_spaces}{active_issue}{active_issue_spaces}{active_issue_space} │
├──────────────┬─{dashlength}─┤
│      Created │ {created}{created_spaces} │
│      Updated │ {updated}{updated_spaces} │
│         Type │ {issuetype}{task_type_spaces} │
│       Status │ {status}{status_spaces} │
│ Story Points │ {story_points}{story_points_spaces} │
│     Priority │ {priority}{priority_spaces} │
│    Component │ {component}{component_spaces} │
│      Version │ {version}{version_spaces} │
│     Assignee │ {assignee}{assignee_spaces} │
│     Reporter │ {reporter}{reporter_spaces} │
└──────────────┴─{dashlength}─┘
Summary
{summary}

Description
{description}

Comments
{comments}'''

        self.set_report_lines(report, description, issue)

        return report.format(**locals())

    def get_reporters(self):
        '''
        Get my issues with JQL
        '''

        self.get_users()

    def get_servers(self):
        '''
        Get list of servers
        '''

        for server in self.vira_servers.keys():
            print(server)

    def get_statuses(self):
        '''
        Get my issues with JQL
        '''

        statuses = []
        for status in self.jira.statuses():
            if str(status) not in statuses:
                statuses.append(str(status))
                print(str(status))

    def get_set_status(self):
        '''
        Get my issues with JQL
        '''

        self.get_statuses()

    def get_version(self):
        '''
        Get my issues with JQL
        '''

        self.get_versions()

    def get_users(self):
        '''
        Get my issues with JQL
        '''

        query = 'ORDER BY updated DESC'
        issues = self.jira.search_issues(query,
                                         fields='assignee, reporter',
                                         json_result='True',
                                         maxResults=-1)

        users = []
        for issue in issues["issues"]:

            id = str(issue['fields']['reporter']['self']).split("=")[1]
            user = issue['fields']['reporter']['displayName']
            if user + ' ~ ' + id not in users:
                users.append(user + ' ~ ' + str(id))

        for user in sorted(users):
            print(user)
        print('Unassigned')

    def get_versions(self):
        '''
        Build a vim popup menu for a list of versions
        '''

        for version in self.jira.project_versions(
                self.userconfig_filter['project']):
            print(version.name)
        print('null')

    def load_project_config(self):
        '''
        Load project configuration for the current git repo

        For example, an entry in projects.yaml may be:

        vira:
          server: https://jira.tgall.ca
          project_name: VIRA
        '''

        # Only proceed if projects file parsed successfully
        if not getattr(self, 'vira_projects', None):
            return

        repo = run_command(
            'git rev-parse --show-toplevel')['stdout'].strip().split('/')[-1]

        # If curren't repo doesn't exist, use __default__ project config if it exists
        if not self.vira_projects.get(repo):
            if self.vira_projects.get('__default__'):
                repo = '__default__'
            else:
                return

        # Set server
        server = self.vira_projects.get(repo, {}).get('server')
        if server:
            vim.command(f'let g:vira_serv = "{server}"')

        # Set user-defined filters for current project
        for key in self.userconfig_filter.keys():
            value = self.vira_projects.get(repo, {}).get('filter', {}).get(key)
            if value:
                self.userconfig_filter[key] = value

        # Set user-defined new-issue defaults for current project
        for key in self.userconfig_newissue.keys():
            value = self.vira_projects.get(repo, {}).get('newissue',
                                                         {}).get(key)
            if value:
                self.userconfig_newissue[key] = value

    def query_issues(self):
        '''
        Query issues based on current filters
        '''

        q = []
        for filterType in self.userconfig_filter.keys():
            filter_str = self.filter_str(filterType)
            if filter_str:
                q.append(filter_str)

        query = ' AND '.join(q) + ' ORDER BY updated DESC'
        issues = self.jira.search_issues(
            query,
            fields='summary,comment,status,statusCategory,issuetype,assignee',
            json_result='True',
            maxResults=vim.eval('g:vira_issue_limit'))

        return issues['issues']

    def reset_filters(self):
        '''
        Reset filters to their default values
        '''

        self.userconfig_filter = dict(self.userconfig_filter_default)

    def set_report_lines(self, report, description, issue):
        '''
        Create dictionary for vira report that shows relationship
        between line numbers and fields to be edited
        '''

        writable_fields = {
            'Assignee': 'ViraSetAssignee',
            'Component': 'ViraSetComponent',
            'Priority': 'ViraSetPriority',
            'Status': 'ViraSetStatus',
            'Type': 'ViraSetType',
            'Version': 'ViraSetVersion',
            'Summary': 'ViraEditSummary',
        }

        self.report_lines = {}

        for idx, line in enumerate(report.split('\n')):
            for field, command in writable_fields.items():
                if field in line:
                    self.report_lines[idx + 1] = command
                    if field == 'Summary':
                        self.report_lines[idx + 2] = command
                        self.report_lines[idx + 3] = command
                    continue

        description_len = description.count('\n') + 3
        for x in range(18, 18 + description_len):
            self.report_lines[x] = 'ViraEditDescription'

        offset = 2 if len(issue['comment']['comments']) > 4 else 1
        comment_line = 18 + description_len + offset
        for comment in issue['comment']['comments']:
            comment_len = comment['body'].count('\n') + 3
            for x in range(comment_line, comment_line + comment_len):
                self.report_lines[x] = 'ViraEditComment ' + comment['id']
            comment_line = comment_line + comment_len

    def write_jira(self):
        '''
        Write to jira
        Can be issue name, description, comment, etc...
        '''

        # User input
        issue = vim.eval('g:vira_active_issue')
        input_stripped = vim.eval('g:vira_input_text').replace(
            self.prompt_text_commented.strip(), '').strip()

        # Check if anything was actually entered by user
        if input_stripped == '':
            print("No vira actions performed")
            return

        if self.prompt_type == 'add_comment':
            return self.jira.add_comment(issue, input_stripped)
        if self.prompt_type == 'edit_comment':
            return self.active_comment.update(body=input_stripped)
        elif self.prompt_type == 'summary':
            return self.jira.issue(issue).update(summary=input_stripped)
        elif self.prompt_type == 'description':
            return self.jira.issue(issue).update(description=input_stripped)
        elif self.prompt_type == 'issue':
            return self.create_issue(input_stripped)
Пример #27
0
class Jira(object):
    """Python-Jira API class"""
    def __init__(self, args):
        """Init"""
        self.jira_server = 'https://company.jira.net'
        self.jira = JIRA('%s' % self.jira_server,
                         basic_auth=(args.jira_user, args.jira_pswd))

        # Arguments
        self.release_name = args.release_name
        self.release_date = args.release_date
        self.project = args.project
        self.status = args.status
        self.fix_version = args.fix_version
        self.tkts_resolved = args.tkts_resolved
        self.verbose = args.verbose

        self.issues = []

    def search_issues(self):
        """Return issues searched with JQL"""
        search_str = 'project = %s AND status = %s AND fixVersion = %s order by lastViewed DESC' % \
                     (self.project, self.status, self.fix_version)

        return self.jira.search_issues(search_str)

    def is_version_exist(self):
        """Check if version exist"""
        # New version is in the last element of the list
        version = self.jira.project_versions(self.project)[-1]

        if str(version) == self.release_name:
            return True
        else:
            return False

    def create_version(self):
        """Create Jira release version"""
        descrp = []

        # Adding tickets summary to release description
        for tkt in self.issues:
            descrp.append((tkt.fields.summary).encode('UTF8'))

        descrp = '. '.join(descrp)

        self.jira.create_version(self.release_name,\
                                 self.project,\
                                 description=descrp,\
                                 releaseDate=self.release_date,\
                                 startDate=None,\
                                 archived=False,\
                                 released=True)

    def tickets_resolved_log(self):
        """Create a file with resolved tickets summary"""
        version = self.jira.project_versions(self.project)[-1]
        release_url = '%s/projects/%s/versions/%s/tab/release-report-all-issues/' % (
            self.jira_server, self.project, str(version.id))

        hdr_str = "======================================================\n" \
                  "The new Fixes or Features added in this release are:\n" \
                  "\n" \
                  "%s\n\n" % release_url

        tail_str = "======================================================"

        with open(self.tkts_resolved, "a") as tkts_file:
            tkts_file.write(hdr_str)
            for tkt in self.issues:
                tkts_file.write("%s: %s \n" % (tkt.key, tkt.fields.summary))
            tkts_file.write(tail_str)

    def update_issue_fix_version(self):
        """ Update fixVersion of tickets"""
        fixVersion = []

        fixVersion.append({'name': self.release_name})
        for tkt in self.issues:
            tkt.update(fields={'fixVersions': fixVersion})

    def create_release(self):
        """ Workflow to create release:
            1. Search tickets for release
            2. Create Jira release version
            3. Update fixVersion of tickets
            4. Parse tickets resolved to file for release announcment
        """
        self.issues = self.search_issues()

        version_exist = self.is_version_exist()
        if version_exist == False:
            self.create_version()

        self.update_issue_fix_version()

        self.tickets_resolved_log()

    def command(self, args):
        """Commands for Jira release"""
        if 'create_release' in args.command:
            self.create_release()

        elif 'close_release' in args.command:
            self.close_release()
Пример #28
0
class jira_handler:
    def __init__(self, project_name):
        self.mongo_db = mongodb_class.mongoDB()
        self.jira = JIRA('http://172.16.60.13:8080',
                         basic_auth=('shenwei', 'sw64419'))
        self.gh = GreenHopper({'server': 'http://172.16.60.13:8080'},
                              basic_auth=('shenwei', 'sw64419'))
        self.name = project_name
        self.project = self.jira.project(self.name)
        self.pj_name = u"%s" % self.project.name
        self.pj_manager = u"%s" % self.project.lead.displayName
        """获取项目版本信息
        """
        _versions = self.jira.project_versions(self.name)
        self.version = {}
        for _v in _versions:
            _key = (u"%s" % _v).replace('.', '^')
            if not self.version.has_key(_key):
                self.version[_key] = {}
            self.version[_key][u"id"] = _v.id
            self.version[_key]['startDate'] = ""
            self.version[_key]['releaseDate'] = ""
            if 'startDate' in dir(_v):
                self.version[_key]['startDate'] = _v.startDate
            if 'releaseDate' in dir(_v):
                self.version[_key]['releaseDate'] = _v.releaseDate
            if self.mongo_db.get_count("project", {"version": _key}) > 0:
                self.mongo_db.handler(
                    "project", "update", {"version": _key},
                    dict({"version": _key}, **self.version[_key]))
            else:
                _val = dict({"version": _key}, **self.version[_key])
                print _val
                self.mongo_db.handler("project", "insert", _val)
        self.issue = None

    def _get_board(self):
        _boards = self.jira.boards()
        for _b in _boards:
            if self.name in _b.name:
                return _b.id
        return None

    def get_current_sprint(self):
        """
        获取本阶段sprint名称
        :return: 返回状态为ACTIVE的sprint的名称
        """
        _b_id = self._get_board()
        if type(_b_id) is not types.NoneType:
            _sprints = self.jira.sprints(_b_id)
            for _s in _sprints:
                if _s.state == 'ACTIVE':
                    return _s.name
        return None

    def get_sprint(self):
        if "customfield_10501" in self.issue.raw['fields'] and \
                type(self.issue.fields.customfield_10501) is not types.NoneType:
            return u'%s' % self.issue.fields.customfield_10501[0].split(
                'name=')[1].split(',')[0]
        return None

    def get_versions(self):
        _v = {}
        for _k in self.version:
            _key = (u"%s" % _k).replace('^', '.')
            _v[_key] = self.version[_k]
        return _v

    def get_pj_info(self):
        return {'pj_name': self.pj_name, 'pj_manager': self.pj_manager}

    def set_issue_by_name(self, issue_id):
        self.issue = self.jira.issue(issue_id)

    def print_green_hopper(self):
        _f = self.gh.fields()
        for __f in _f:
            __cns = __f['clauseNames']
            print('-' * 8)
            for _n in __cns:
                print u"name: %s" % _n
            print "id: ", u"%s" % __f['id']
            print "name: ", u"%s" % __f['name']

    def get_story_point(self):
        """
        获取Issue(story)的预置成本, 1 point = 4 hours
        :return: 预置成本
        """
        if "customfield_10304" in self.issue.raw['fields'] and \
                type(self.issue.fields.customfield_10304) is not types.NoneType:
            return self.issue.fields.customfield_10304
        return None

    def get_task_time(self):
        return {
            "agg_time": self.issue.fields.aggregatetimeestimate,
            "org_time": self.issue.fields.timeoriginalestimate,
            "spent_time": self.issue.fields.timespent
        }

    def get_landmark(self):
        if len(self.issue.fields.fixVersions) > 0:
            return u"%s" % self.issue.fields.fixVersions[0]
        if len(self.issue.fields.versions) > 0:
            print self.show_name(
            ), " version: %s" % self.issue.fields.versions[0]
            return u"%s" % self.issue.fields.versions[0]
        return ""

    def get_desc(self):
        return u"%s" % self.issue.fields.summary

    def show_name(self):
        return u"%s" % str(self.issue)

    def get_type(self):
        return u"%s" % self.issue.fields.issuetype

    def get_status(self):
        return u"%s" % self.issue.fields.status

    def get_subtasks(self):
        """
        收集issue的相关子任务的issue
        :return: 相关issue字典
        """
        link = {}
        if not link.has_key(self.show_name()):
            link[self.show_name()] = []
        _task_issues = self.issue.fields.subtasks
        for _t in _task_issues:
            link[self.show_name()].append(u"%s" % _t)
        return link

    def get_child_requirement(self):

        link = []
        jql = "issue in  childrenOfParentRequirement('%s')" % self.show_name()
        # print jql
        tot = 0
        while True:
            issues = self.jira.search_issues(jql, maxResults=100, startAt=tot)
            for issue in issues:
                link.append(issue.key)
            if len(issues) == 100:
                tot += 100
            else:
                break
        return link

    def get_link(self):
        """
        收集issue的相关issue
        :return: 相关issue字典
        """
        link = {}
        if self.show_name() not in link:
            link[self.show_name()] = []
        """兼容以前: 与story相关的task是通过issulelinks关联的"""
        _task_issues = self.issue.fields.issuelinks
        for _t in _task_issues:
            if "outwardIssue" in dir(_t):
                """该story相关的任务"""
                link[self.show_name()].append(u"%s" % _t.outwardIssue)
            if "inwardIssue" in dir(_t):
                """该story相关的任务"""
                link[self.show_name()].append(u"%s" % _t.inwardIssue)
        """采用synapseRT插件后对需求的管理"""
        _task_issues = self.get_child_requirement()
        for _t in _task_issues:
            link[self.show_name()].append(_t)

        return link

    def show_issue(self):
        """
        显示issue信息
        :return:
        """
        print("[%s]-%s" % (self.show_name(), self.get_desc())),
        print u"类型:%s" % self.get_type(),
        print(u'状态:%s' % self.get_status()),

        print u"里程碑:%s" % self.get_landmark(),
        _time = self.get_task_time()
        """
        if type(_time['agg_time']) is types.NoneType:
            _time['agg_time'] = ""
        if type(_time['org_time']) is types.NoneType:
            _time['org_time'] = ""
        if type(_time["spent_time"]) is types.NoneType:
            _time["spent_time"] = ""
        """
        if "customfield_11300" in self.issue.raw['fields'] and \
                type(self.issue.fields.customfield_11300) is not types.NoneType:
            _epic_link = self.issue.raw['fields']["customfield_11300"]
        else:
            _epic_link = None
        _issue = {
            u"%s" % self.show_name(): {
                "issue_type": self.get_type(),
                "created": self.issue.fields.created,
                "updated": self.issue.fields.updated,
                "lastViewed": self.issue.fields.lastViewed,
                "users": self.get_users(),
                "status": self.get_status(),
                "landmark": self.get_landmark(),
                "point": self.get_story_point(),
                "agg_time": _time['agg_time'],
                "org_time": _time['org_time'],
                "summary": self.issue.fields.summary,
                "spent_time": _time['spent_time'],
                "sprint": self.get_sprint(),
                "epic_link": _epic_link
            }
        }
        _key = u"%s" % self.show_name()
        if self.mongo_db.get_count("issue", {"issue": _key}) > 0:
            self.mongo_db.handler("issue", "update", {"issue": _key},
                                  dict({"issue": _key}, **_issue[_key]))
        else:
            self.mongo_db.handler("issue", "insert",
                                  dict({"issue": _key}, **_issue[_key]))
        if self.mongo_db.get_count("issue_link", {"issue": _key}) > 0:
            self.mongo_db.handler("issue_link", "update", {"issue": _key},
                                  dict({"issue": _key}, **self.get_link()))
        else:
            self.mongo_db.handler("issue_link", "insert",
                                  dict({"issue": _key}, **self.get_link()))

        return _issue

    def get_users(self):
        """
        获取访问issue的用户
        2018.3.1:改为 经办人 assignee
        :return:
        watcher = self.jira.watchers(self.issue)
        _user = u"%s" % (', '.join(watcher.displayName for watcher in watcher.watchers))
        """
        if type(self.issue.raw['fields']["assignee"]) is types.NoneType:
            return None
        return (u"%s" %
                self.issue.raw['fields']["assignee"]['displayName']).replace(
                    ' ', '')

    def write_log(self, info):
        self.mongo_db.handler("log", "insert", info)

    def write_worklog(self, info):
        _search = {
            'issue': info['issue'],
            'author': info['author'],
            'updated': info['updated']
        }
        self.mongo_db.handler('worklog', 'update', _searchinfo)

    def sync_worklog(self):
        worklogs = self.jira.worklogs(self.show_name())
        wl = {}
        for worklog in worklogs:
            wl['issue'] = self.show_name()
            wl['author'] = u'%s' % worklog.author
            wl['comment'] = u'%s' % worklog.comment
            wl['timeSpent'] = worklog.timeSpent
            wl['timeSpentSeconds'] = worklog.timeSpentSeconds
            wl['updated'] = worklog.updated
            self.write_worklog(wl)

    def scan_issue(self, bg_date, keys, version):
        """
        扫描project收集相关版本的issue信息
        :param bg_date: 起始日期,如 2018-1-31
        :param keys: 关键字,[u'story', u'故事']
        :param version: 版本,里程碑
        :return: 按issue类型进行统计值kv_sum,issue链kv_link,相关任务链task_link
        """
        jql_sql = u'project=%s AND created >= %s ORDER BY created DESC' % (
            self.name, bg_date)
        total = 0
        kv_sum = {}
        kv_link = {}
        task_link = {}

        while True:
            issues = self.jira.search_issues(jql_sql,
                                             maxResults=100,
                                             startAt=total)
            for issue in issues:

                if (u"%s" % issue.fields.issuetype) not in keys:
                    continue

                self.issue = issue

                if ((u"%s" % issue.fields.issuetype) == 'story' and
                        (self.get_landmark() == version or u'入侵' in self.issue.fields.summary)) or \
                        (u"%s" % issue.fields.issuetype) in [u'epic', u'improvement',
                                                             u'New Feature', u'改进', u'新功能']:
                    """收集story相关的任务"""
                    task_link.update(self.get_link())

                    _type = self.get_type()
                    _status = self.get_status()
                    if not kv_sum.has_key(_type):
                        kv_sum[_type] = 0
                        kv_link[_type] = {}

                    kv_sum[_type] += 1
                    if not (kv_link[_type]).has_key(_status):
                        (kv_link[_type])[_status] = []
                    (kv_link[_type])[_status].append(self.show_name())

            if len(issues) == 100:
                total += 100
            else:
                break
        return kv_sum, kv_link, task_link