Ejemplo n.º 1
0
    def for_qa_team(self, issue_key: str) -> bool:
        """Similar to passed_qa but checks if a QA tester moved this at any point to make sure it actually got tested.

        :param issue_key: a Jira story key
        :return boolean: True if the story has a status change with a valid QA tester, otherwise False
        """
        try:
            issue = self.jira.issue(issue_key,
                                    fields='status',
                                    expand='changelog')
        except JIRAError as e:
            raise JiraBoardException(
                f'[!] {str(e)}: Failed to get Jira issue.')
        else:
            if issue is None:
                raise JiraBoardException('[!] NoneType issue is not valid.')
            else:
                # Let's try this another way
                statuses = self.get_statuses(issue.changelog.histories)
                tester = next(
                    filter(
                        lambda s: s['authorName'] in
                        self.testers and 'qa release' in s['fromString'].lower(
                        ) and 'qa testing' in s['toString'].lower(), statuses),
                    None)
                return tester is not None
Ejemplo n.º 2
0
    def get_subtasks(self, issue_key: str) -> list:
        """Get subtasks of a Jira story.

        :param issue_key: a Jira story key
        :return list: a list of subtask names
        """
        try:
            issue = self.jira.issue(issue_key,
                                    fields='status',
                                    expand='changelog')
        except JIRAError as e:
            raise JiraBoardException(
                f'[!] {str(e)}: Failed to get Jira issue.')
        else:
            if issue is None:
                raise JiraBoardException('[!] NoneType issue is not valid.')
            else:
                return [task.lower() for task in issue.fields.subtasks]
Ejemplo n.º 3
0
    def get_labels(self, issue_key: str) -> list:
        """Get a list of labels attached to a story by issue_key.

        :param issue_key: a Jira story key
        :return list: a list Jira story labels
        """
        try:
            issue = self.jira.issue(issue_key,
                                    fields='labels',
                                    expand='changelog')
        except JIRAError as e:
            raise JiraBoardException(
                f'[!] {str(e)}: Failed to get Jira issue.')
        else:
            if issue is None:
                raise JiraBoardException('[!] NoneType issue is not valid.')
            else:
                return [label.lower() for label in issue.fields.labels]
Ejemplo n.º 4
0
    def is_currently_failed(self, issue_key: str) -> bool:
        """Given an issue_key, check if a Jira story currently has a failure status.

        :param issue_key: a Jira story key
        :return boolean: True if the story has a current fail status, otherwise False
        """
        current_status = self.get_current_status(issue_key)
        if current_status is None:
            raise JiraBoardException('[!] No current status found')
        return current_status in ['In Progress', 'Backlog']
Ejemplo n.º 5
0
    def has_complete_status(self, issue_key: str) -> bool:
        """Given am issue_key, check if the story has a 'Complete' status category.

        :param issue_key: a Jira story key
        :return boolean: True if the story has a current status category of 'Done', otherwise False
        """
        current_status_category = self.get_current_status_category(issue_key)
        if current_status_category is None:
            raise JiraBoardException('[!] No current status category found')
        return current_status_category == 'Done'
Ejemplo n.º 6
0
    def is_in_qa_testing(self, issue_key: str) -> bool:
        """Given am issue_key, check if the story is currently in 'QA Testing'.

        :param issue_key: a Jira story key
        :return boolean: True if the story has a current status of 'QA Testing', otherwise False
        """
        current_status = self.get_current_status(issue_key)
        if current_status is None:
            raise JiraBoardException('[!] No current status found')
        return current_status == 'QA Testing'
Ejemplo n.º 7
0
    def get_issue(self, issue_key: str, fields: str = 'status') -> Issue:
        """Get a Jira story given a key.

        :param issue_key: a Jira story key
        :param fields:
        :return:
        """
        try:
            issue = self.jira.issue(issue_key,
                                    fields=fields,
                                    expand='changelog')
        except JIRAError as e:
            raise JiraBoardException(
                f'[!] {str(e)}: Failed to get Jira issue.')
        else:
            if issue is None:
                raise JiraBoardException(
                    f'[!] Unable to retrieve issue {issue_key}')
            return issue
Ejemplo n.º 8
0
    def get_current_sprint(self, board_id: int) -> Sprint:
        """Given a board_id, get the current sprint the project is in. Also try and filter out that Moodle shit.

        :param board_id: ID for the current Jira board
        :return:
        """
        try:
            sprints = self.jira.sprints(board_id)
        except JIRAError as e:
            raise JiraBoardException(
                f'[!] {str(e)}: Failed to get Jira sprints.')
        else:
            res = next(
                filter(
                    lambda story: story.state.lower() == 'active' and 'yaks' in
                    story.name.lower(), sprints), None)
            if res is None:
                raise JiraBoardException('[!] No current sprint')
            return res
Ejemplo n.º 9
0
    def is_stale_qa_ready(self, issue_key: str) -> bool:
        """Given am issue_key, check if the story is ready for QA after previously failing testing.

        :param issue_key: a Jira story key
        :return boolean: True if the story has a current status of 'Ready for QA Release' and at least one prior fail status, otherwise False
        """
        current_status = self.get_current_status(issue_key)
        qa_fail = self.has_failed_qa(issue_key)
        if current_status is None:
            raise JiraBoardException('[!] No current status found')
        return current_status == 'Ready for QA Release' and qa_fail
Ejemplo n.º 10
0
    def is_fresh_qa_ready(self, issue_key: str) -> bool:
        """Given am issue_key, check if the story is ready for QA for the very first time.

        :param issue_key: a Jira story key
        :return boolean: True if the story has a current status of "Ready for QA Release" and no fail statuses in the past, otherwise False
        """
        current_status = self.get_current_status(issue_key)
        qa_fail = self.has_failed_qa(issue_key)
        if current_status is None:
            raise JiraBoardException('[!] No current status found')
        return current_status == 'Ready for QA Release' and not qa_fail
Ejemplo n.º 11
0
    def get_attachments(self, issue_key: str) -> list:
        """Get attachments from a Jira story.

        :param issue_key: a Jira story key
        :return list: a list of URLs pointing to story attachments
        """
        try:
            issue = self.jira.issue(issue_key,
                                    fields='attachment',
                                    expand='changelog')
        except JIRAError as e:
            raise JiraBoardException(
                f'[!] {str(e)}: Failed to get Jira issue.')
        else:
            if issue is None:
                raise JiraBoardException('[!] NoneType issue is not valid.')
            else:
                return [
                    '{}secure/attachment/{}/{}'.format(self.host, a.id,
                                                       a.filename)
                    for a in issue.fields.attachment
                ]
Ejemplo n.º 12
0
    def get_jql_filter(self, filter_id: int) -> Filter:
        """

        :param filter_id:
        :return:
        """
        try:
            j_filter = self.jira.filter(filter_id)
        except JIRAError as e:
            raise JiraBoardException(
                f'[!] {str(e)}: Failed to get Jira filter.')
        else:
            return j_filter
Ejemplo n.º 13
0
    def get_issues_from_filter(self, filter_id: int) -> list:
        """Get Jira issues returned by a given JQL filter_id.

        :param filter_id: id for the filter
        :return:
        """
        jql = self.get_jql_filter(filter_id).jql
        try:
            issues = self.jira.search_issues(jql, maxResults=100)
        except JIRAError as e:
            raise JiraBoardException(
                f'[!] {str(e)}: Failed to get issues from Jira filter.')
        else:
            return issues
Ejemplo n.º 14
0
    def add_new_filter(self, filter_name: str, new_query: str) -> Filter:
        """Add a new JQL filter to the Jira project.

        :param filter_name: name for the filter
        :param new_query: filter criteria in the form of a JQL string.
        :return:
        """
        try:
            result = self.jira.create_filter(filter_name, jql=new_query)
        except JIRAError as e:
            raise JiraBoardException(
                f'[!] {str(e)}: Failed to get Jira stories.')
        else:
            return result
Ejemplo n.º 15
0
    def passed_qa(self, issue_key: str) -> bool:
        """Given an issue_key, check if the story has passed QA.

        :param issue_key: a Jira story key
        :return boolean: True if the story has a passing status with a valid QA tester, otherwise False
        """
        try:
            issue = self.jira.issue(issue_key,
                                    fields='status',
                                    expand='changelog')
        except JIRAError as e:
            raise JiraBoardException(
                f'[!] {str(e)}: Failed to get Jira issue.')
        else:
            if issue is None:
                raise JiraBoardException('[!] NoneType issue is not valid.')
            else:
                statuses = self.get_statuses(issue.changelog.histories)
                tester = next(
                    filter(lambda s: s['authorName'] in self.testers,
                           statuses), None)
                return self.has_complete_status(
                    issue.key) and tester is not None
Ejemplo n.º 16
0
    def get_project(self) -> Project:
        """Get active Jira project.

        :return: JSON object representing entire Jira project, otherwise None
        """
        try:
            projects = self.jira.projects()
        except JIRAError as e:
            raise JiraBoardException(
                f'[!] {str(e)}: Failed to get Jira projects.')
        else:
            return next(
                filter(lambda proj: proj.key.upper() == self.project_key,
                       projects), None)
Ejemplo n.º 17
0
    def update_filter(self, filter_id: int, new_query: str) -> Filter:
        """Update an existing JQL filter associated with the Jira project.

        :param filter_id: id for the filter
        :param new_query: filter criteria in the form of a JQL string.
        :return:
        """
        try:
            result = self.jira.update_filter(filter_id, jql=new_query)
        except JIRAError as e:
            raise JiraBoardException(
                f'[!] {str(e)}: Failed to update Jira filter.')
        else:
            return result
Ejemplo n.º 18
0
    def has_failed_qa(self, issue_key: str) -> bool:
        """Given an issue_key, check if the story has failed QA in the past.

        :param issue_key: a Jira story key
        :return boolean: True if the story has a fail status in the past, otherwise False
        """
        try:
            issue = self.jira.issue(issue_key,
                                    fields='status',
                                    expand='changelog')
        except JIRAError as e:
            raise JiraBoardException(
                f'[!] {str(e)}: Failed to get Jira issue.')
        else:
            if issue is None:
                raise JiraBoardException('[!] NoneType issue is not valid.')
            else:
                statuses = self.get_statuses(issue.changelog.histories)
                failStatus = next(
                    filter(
                        lambda status: (status['authorName'] in self.testers)
                        and status['fromString'] == 'QA Testing' and status[
                            'toString'] == 'In Progress', statuses), None)
                return True if failStatus is not None else False
Ejemplo n.º 19
0
    def get_board(self, project_key: str) -> Board:
        """Get active Jira board given a project_key.

        :param project_key: Jira project key
        :return: JSON object representing entire Jira board, otherwise None
        """
        try:
            boards = self.jira.boards()  # (project_key)
        except JIRAError as e:
            raise JiraBoardException(
                f'[!] {str(e)}: Failed to get Jira projects.')
        else:
            return next(
                filter(
                    lambda board: board.name.lower() == 'medhub development',
                    boards), None)
Ejemplo n.º 20
0
    def get_issues(self, filter_id: int) -> list:
        """Seems weirdly like a dupe of get_issues_from_filter(), but if things are working I don't wanna break it right now..

        :param filter_id: id for the filter
        :return:
        """
        if self.testMode:
            jsonData = open(os.path.join('testJiraData.json'),
                            'r',
                            encoding='utf-8')
            issues = json.loads(jsonData.read())
        else:
            try:
                issues = self.get_issues_from_filter(filter_id)
            except JIRAError as e:
                raise JiraBoardException(
                    f'[!] {str(e)}: Failed to get issues from Jira filter.')
        return issues
Ejemplo n.º 21
0
    def get_parsed_stories(self,
                           raw_issues: list,
                           testrail_mode: bool = False) -> list:
        """Given a collection of raw Jira stories, parses them down to JSON objects containing needed fields only.

        :param raw_issues: JSON collection of stories returned from the Jira API
        :param testrail_mode: Should QA dates be ignored? If they're in the release but haven't been QAed yet... yes.
        :return parsed_stories: a list of parsed stories ready for one of the reconcile methods
        """
        parsed_stories = []

        for issue in raw_issues:
            _story = self.get_issue(
                issue.key,
                fields=
                'summary,description,comment,labels,created,updated,status')
            _testedBy = 'unassigned'
            _hasFailed = False
            _currentStatus = self.get_current_status(_story.key)
            _changelog = sorted(_story.changelog.histories,
                                key=lambda ch: ch.created,
                                reverse=True)
            _statuses = self.get_statuses(_changelog)
            _validTester = next(
                filter(lambda status: status['authorName'] in self.testers,
                       _statuses), None)
            _testedBy = _validTester[
                'authorName'] if _validTester is not None else 'unassigned'
            _url = urljoin(self.host, 'browse/{}'.format(_story.key))
            _labels = self.get_labels(_story.key)

            if _story.fields.summary is not None:
                _summary = ''.join(
                    re.findall(r'[^*`#\t\'"]', _story.fields.summary))
            else:
                _summary = ''

            if _story.fields.description is not None:
                _desc = ''.join(
                    re.findall(r'[^*`#\t\'"]', _story.fields.description))
            else:
                _desc = ''

            if testrail_mode:
                record = dict(
                    jira_key=_story.key,
                    jira_url=_url,
                    jira_summary=_summary,
                    jira_desc=_desc,
                    labels=_labels,
                    jira_created=_story.fields.created,
                    jira_updated=_story.fields.updated,
                    tested_by=_testedBy,
                )

            else:
                _qaStatuses = list(
                    filter(
                        lambda status: status['fromString'] == 'QA Testing' or
                        status['toString'] == 'QA Testing', _statuses))
                _validTester = next(
                    filter(lambda status: status['authorName'] in self.testers,
                           _statuses), None)

                _qaDateStatus = next(
                    filter(
                        lambda status: status['fromString'] == 'In Progress'
                        and status['toString'] == 'Ready for QA Release' and
                        _story.key not in parsed_stories, _statuses), None)

                if _qaDateStatus is None:
                    if self.is_defect(_story.key) or self.is_qa_task(
                            _story.key):
                        # defects or QA tasks don't go on the board
                        continue

                    print(
                        f'[!] QA date not found on {str(issue.key)}\n\tEither the issue type did not get excluded or a developer may have accidentally moved the story into QA'
                    )
                    _movedToQaDate = dateutil.parser.parse(
                        _story.fields.updated)
                else:
                    _movedToQaDate = dateutil.parser.parse(
                        _qaDateStatus['created'])

                _hasFailed = True if len(_qaStatuses) > 0 else False

                _testedBy = _validTester[
                    'authorName'] if _validTester is not None else 'unassigned'

                _comments = '\n'.join([
                    '\n_**{}** at {}_:\n\n{}\n\n'.format(
                        comment.author,
                        dateutil.parser.parse(
                            comment.updated).strftime('%Y-%m-%d %H:%M'),
                        ''.join(re.findall(r'[^*`#\t\'"]', comment.body)))
                    for comment in sorted(_story.fields.comment.comments,
                                          key=lambda s: s.updated,
                                          reverse=True)
                ])

                _statuses = '\n'.join([
                    '_**{}**_:\n\nFrom {} to {} at {}\n\n'.format(
                        s['authorName'], s['fromString'], s['toString'],
                        dateutil.parser.parse(
                            s['created']).strftime('%Y-%m-%d %H:%M'))
                    for s in _statuses
                ])

                if _movedToQaDate is not None:
                    _qaDate = _movedToQaDate.strftime('%Y-%m-%d %H:%M:%S%z')
                else:
                    raise JiraBoardException('[!] No QA date status found')

                _api_url = urljoin(self.host,
                                   'rest/api/2/issue/{}'.format(_story.key))
                _hotfix = self.is_hotfix(_story.key)
                _inStaging = False
                _attachments = self.get_attachments(_story.key)

                record = dict(
                    jira_id=issue.id,
                    jira_key=_story.key,
                    jira_url=_url,
                    jira_api_url=_api_url,
                    jira_summary=_summary,
                    jira_desc=_desc,
                    jira_created=_story.fields.created,
                    jira_updated=_story.fields.updated,
                    jira_qa_date=_qaDate,
                    tested_by=_testedBy,
                    current_status=_currentStatus,
                    has_failed=_hasFailed,
                    in_staging=_inStaging,
                    is_hotfix=_hotfix,
                    comments=_comments,
                    statuses=_statuses,
                    labels=_labels,
                    attachments=_attachments,
                    last_known_commit_date=None,
                    git_commit_message=None,
                )

            parsed_stories.append(record)

        if testrail_mode:
            result = sorted(parsed_stories,
                            key=lambda story: story['jira_updated'])
        else:
            result = sorted(parsed_stories,
                            key=lambda story: story['jira_qa_date'])
        return result