Пример #1
0
def link_bug_to_site(bug_key: str, site_key: str) -> None:
    print("Linking bug '{}' with site '{}".format(bug_key, site_key))
    jira = JIRA(settings.JIRA_URL, basic_auth=(settings.JIRA_USERNAME, settings.JIRA_PASSWORD))

    jira.create_issue_link(
        type="blocks",
        inwardIssue=bug_key,
        outwardIssue=site_key,
        comment={
            "body": "Linking '%s' --> '%s'" % (bug_key, site_key),
        }
    )
Пример #2
0
class JiraClient:
    """Wrapper around Jira client."""
    def __init__(self,
                 jira_board: Mapping[str, Any],
                 settings: Optional[Mapping] = None):
        self.secret_reader = SecretReader(settings=settings)
        self.project = jira_board["name"]
        jira_server = jira_board["server"]
        self.server = jira_server["serverUrl"]
        token = jira_server["token"]
        token_auth = self.secret_reader.read(token)
        self.jira = JIRA(self.server, token_auth=token_auth)

    def get_issues(self, fields: Optional[Mapping] = None) -> GottenIssue:
        block_size = 100
        block_num = 0

        all_issues: GottenIssue = []
        jql = "project={}".format(self.project)
        kwargs: dict[str, Any] = {}
        if fields:
            kwargs["fields"] = ",".join(fields)
        while True:
            index = block_num * block_size
            issues = self.jira.search_issues(jql, index, block_size, **kwargs)
            all_issues.extend(issues)
            if len(issues) < block_size:
                break
            block_num += 1

        return all_issues

    def create_issue(
            self,
            summary: str,
            body: str,
            labels: Optional[Iterable[str]] = None,
            links: Iterable[str] = (),
    ) -> Issue:
        """Create an issue in our project with the given labels."""
        issue = self.jira.create_issue(
            project=self.project,
            summary=summary,
            description=body,
            labels=labels,
            issuetype={"name": "Task"},
        )
        for ln in links:
            self.jira.create_issue_link(type="is caused by",
                                        inwardIssue=issue.key,
                                        outwardIssue=ln)
        return issue
Пример #3
0
def _link_jiras(
    client: jira.JIRA,
    from_jira: str,
    to_jira: str,
    relation_type: str = DEFAULT_LINK_TYPE,
) -> requests.Response:
    return client.create_issue_link(relation_type, from_jira, to_jira)
Пример #4
0
class Jira:

    # {{{ Constants
    BLOCKS = 'Blocks'
    DUPLICATE = 'Duplicate'
    RELATES = 'Relates'

    # }}}

    # {{{ init(address) - Initialise JIRA class, pointing it to the JIRA endpoint
    def __init__(self, address='https://r3-cev.atlassian.net'):
        self.address = address
        self.jira = None
        self.mock_key = 1
        self.custom_fields_by_name, self.custom_fields_by_key = {}, {}

    # }}}

    # {{{ login(user, password) - Log in as a specific JIRA user
    def login(self, user, password):
        try:
            self.jira = JIRA(self.address, auth=(user, password))
            for x in self.jira.fields():
                if x['custom']:
                    self.custom_fields_by_name[x['name']] = x['key']
                    self.custom_fields_by_key[x['key']] = x['name']
            return self
        except Exception as error:
            message = error.message
            if isinstance(error, JIRAError):
                message = error.text if error.text and len(
                    error.text) > 0 and not error.text.startswith(
                        '<!') else message
            raise Exception('failed to log in to JIRA{}{}'.format(
                ': ' if message else '', message))

    # }}}

    # {{{ search(query) - Search for issues and manually traverse pages if multiple pages are returned
    def search(self, query, *args):
        max_count = 50
        index, offset, count = 0, 0, max_count
        query = query.format(*args) if len(args) > 0 else query
        while count == max_count:
            try:
                issues = self.jira.search_issues(query,
                                                 maxResults=max_count,
                                                 startAt=offset)
                count = len(issues)
                offset += count
                for issue in issues:
                    index += 1
                    yield Issue(self, index=index, issue=issue)
            except JIRAError as error:
                raise Exception('failed to run query "{}": {}'.format(
                    query, error.text))

    # }}}

    # {{{ find(key) - Look up issue by key
    def find(self, key):
        try:
            issue = self.jira.issue(key)
            return Issue(self, issue=issue)
        except JIRAError as error:
            raise Exception('failed to look up issue "{}": {}'.format(
                key, error.text))

    # }}}

    # {{{ create(fields, dry_run) - Create a new issue
    def create(self, fields, dry_run=False):
        if dry_run:
            return Issue(self, fields=fields)
        try:
            fields['labels'] = filter(lambda x: x is not None,
                                      fields['labels'])
            issue = self.jira.create_issue(fields)
            return Issue(self, issue=issue)
        except JIRAError as error:
            raise Exception('failed to create issue: {}'.format(error.text))

    # }}}

    # {{{ link(issue_key, other_issue_key, relationship, dry_run) - Link one issue to another
    def link(self,
             issue_key,
             other_issue_key,
             relationship=RELATES,
             dry_run=False):
        if dry_run:
            return
        try:
            self.jira.create_issue_link(type=relationship,
                                        inwardIssue=issue_key,
                                        outwardIssue=other_issue_key,
                                        comment={
                                            'body':
                                            'Linked {} to {}'.format(
                                                issue_key, other_issue_key),
                                        })
        except JIRAError as error:
            raise Exception('failed to link {} and {}: {}'.format(
                issue_key, other_issue_key, error.text))
def main(credential_file, child_issue, parent_issue, link_type):

    rest_url_file = DEFAULT_URL_FILE
    if not os.path.exists(rest_url_file):
        print("JIRA REST URL file '{}' does not exist".format(rest_url_file))
        sys.exit(1)
    else:
        with open(rest_url_file, 'r') as f:
            url = f.readline()
            url = url.strip()
            print("read the REST URL from file '{}'".format(rest_url_file))

    if credential_file is None:
        credential_file = DEFAULT_CREDENTIAL_FILE

    if not os.path.exists(credential_file):
        print(
            "JIRA credential file '{}' does not exist".format(credential_file))
        sys.exit(1)

    error_ctr = 0

    if child_issue is None:
        print("--child_issue was not specified")
        error_ctr += 1

    if parent_issue is None:
        print("--parent_issue was not specified")
        error_ctr += 1

    if error_ctr > 0:
        print("Required parameter(s) not defined")
        sys.exit(1)

    if link_type is None:
        link_type = DEFAULT_LINK_TYPE
        print(
            "--link_type was not specified and therefore was set to default '{}'"
            .format(link_type))

    with open(credential_file, 'r') as f:
        line = f.readline()
        line = line.strip()
        (username, password) = line.split(':')
        print("read username and password from credentials file '{}'".format(
            credential_file))

    auth_jira = JIRA(url, basic_auth=(username, password))

    if auth_jira is None:
        print("Could not instantiate JIRA for url '{}'".format(url))
        sys.exit(1)

    print("Will attempt to link JIRA issue '{}' to '{}' with link type '{}'".
          format(child_issue, parent_issue, link_type))

    try:

        auth_jira.create_issue_link(type=link_type,
                                    inwardIssue=child_issue,
                                    outwardIssue=parent_issue,
                                    comment={
                                        "body":
                                        "Linking {} to {}".format(
                                            child_issue, parent_issue)
                                    })

    except Error as e:
        print(
            "Encountered some exception while attempting to link '{}' to '{}' with link type '{}': {}"
            .format(child_issue, parent_issue, link_type, e))
        sys.exit(1)
    else:
        print("Linked '{}' to '{}' with link type '{}'".format(
            child_issue, parent_issue, link_type))
Пример #6
0
class Thread(QThread):
    '''Threaded process to send data to jira'''
    add_log_post = pyqtSignal(str)
    set_progress_bar = pyqtSignal(int, int)

    def __init__(self, task_list, config):
        self.config = config
        self.task_list = task_list
        self.options = {'server': self.config["jira_base_url"]}
        self.jira = ""
        QThread.__init__(self)

    def stop(self):
        ''' kills all threads '''
        self.terminate()

    def run(self):
        ''' main process that reads task list, connects to jira, and creates
        every DEV and QA task'''
        if not self.config['project_key']:
            self.add_log_post.emit("Por favor defina uma chave de projeto")
            self.stop()

        self.add_log_post.emit(u'Conectando no Jira...')
        self.jira = JIRA(self.options,
                         basic_auth=(self.config['jira_user'],
                                     self.config['jira_token']))
        for task in self.task_list:
            last_dev_key = None
            if isinstance(task["devpts"],
                          (int, float)) and task["devpts"] >= 0:
                self.add_log_post.emit("Adding Dev Task: ")
                self.add_log_post.emit("+-- Parent: {}".format(task["parent"]))
                self.add_log_post.emit("+-- Title : {}".format(task["title"]))
                self.add_log_post.emit("+-- Points: {}".format(task["devpts"]))
                self.add_log_post.emit("+-- Descri: {}".format(task["desc"]))
                rootnn_dict = {
                    'project': {
                        'key': self.config["project_key"]
                    },
                    'summary': "DEV - {}".format(str(task["title"])),
                    'description': str(task["desc"]),
                    'customfield_10004': int(task["devpts"]),
                    'issuetype': {
                        'name': 'Development Task'
                    },
                    'parent': {
                        'key': str(task["parent"])
                    }
                }
                try:
                    print("dev")
                    print(rootnn_dict)
                    child = self.jira.create_issue(fields=rootnn_dict)
                    last_dev_key = child.key
                    self.add_log_post.emit("+-- SubTask Key: {}".format(
                        child.key))
                except Exception as error:
                    self.add_log_post.emit("{}".format(error))
                    self.stop()
                self.add_log_post.emit(" ")

                # QA Task
            if isinstance(task["qapts"], (int, float)) and task["qapts"] >= 0:
                self.add_log_post.emit("Adding QA Task: ")
                self.add_log_post.emit("+-- Parent: {}".format(task["parent"]))
                self.add_log_post.emit("+-- Title : {}".format(task["title"]))
                self.add_log_post.emit("+-- Points: {}".format(task["qapts"]))
                self.add_log_post.emit("+-- Descri: {}".format(task["desc"]))
                rootnn_dict = {
                    'project': {
                        'key': self.config["project_key"]
                    },
                    'summary': "QA - {}".format(str(task["title"])),
                    'description': str(task["desc"]),
                    'customfield_10004': int(task["qapts"]),
                    'issuetype': {
                        'name': 'QA Task'
                    },
                    'parent': {
                        'key': str(task["parent"])
                    }
                }
                try:
                    print("qa")
                    child = self.jira.create_issue(fields=rootnn_dict)
                    self.add_log_post.emit("+-- SubTask Key: {}".format(
                        child.key))
                except Exception as error:
                    self.add_log_post("{}".format(error))
                if last_dev_key:
                    try:
                        self.jira.create_issue_link(type="depended by",
                                                    inwardIssue=child.key,
                                                    outwardIssue=last_dev_key)
                    except Exception as error:
                        self.add_log_post("{}".format(error))
                self.add_log_post.emit(" ")
}

jira = JIRA(basic_auth=('login', 'pass'),
            options={'server': 'http://example.com'})
for issue in jira.search_issues('Your JQL here'):
    #print('{}: {}: {}: {}'.format(issue.key, issue.fields.summary, issue.fields.customfield_10002, issue.fields.components))
    existingComponents = []
    for component in issue.fields.components:
        existingComponents.append({"name": component.name})
        #print existingComponents
    existingVersion = []
    for version in issue.fields.fixVersions:
        existingVersion.append({"name": version.name})
        #print existingVersion
    issue_dict['summary'] = issue.fields.summary
    issue_dict['customfield_10002'] = issue.fields.customfield_10002
    issue_dict['components'] = existingComponents
    issue_dict['priority'] = {
        'id': issue.fields.priority.id,
        'name': issue.fields.priority.name
    }
    issue_dict['fixVersions'] = existingVersion

    print issue_dict
    new_issue = jira.create_issue(fields=issue_dict)
    #link issues, if need it
    jira.create_issue_link(type="includes",
                           inwardIssue=new_issue.key,
                           outwardIssue=issue.key)
    print(new_issue.key)
Пример #8
0
class AugurJira(object):
    """
    A thin wrapper around the Jira module providing some refinement for things like fields, convenience methods
    for ticket actions and awareness for Augur-specific data types.
    """
    jira = None

    def __init__(self, server=None, username=None, password=None):

        self.logger = logging.getLogger("augurjira")
        self.server = server or settings.main.integrations.jira.instance
        self.username = username or settings.main.integrations.jira.username
        self.password = password or settings.main.integrations.jira.password
        self.fields = None

        self.jira = JIRA(basic_auth=(self.username, self.password),
                         server=self.server,
                         options={"agile_rest_path": "agile"})

        self._field_map = {}

        self._default_fields = munchify({
            "summary": None,
            "description": None,
            "status": None,
            "priority": None,
            "parent": None,
            "resolution": None,
            "epic link": None,
            "dev team": None,
            "labels": None,
            "issuelinks": None,
            "development": None,
            "reporter": None,
            "assignee": None,
            "issuetype": None,
            "project": None,
            "creator": None,
            "attachment": None,
            "worklog": None,
            "story points": None,
            "changelog": None
        })

        self.fields = api.get_memory_cached_data('custom_fields')
        if not self.fields:
            fields = api.memory_cache_data(self.jira.fields(), 'custom_fields')
            self.fields = {f['name'].lower(): munchify(f) for f in fields}

        default_fields = {}
        for df, val in self._default_fields.items():
            default_fields[df] = self.get_field_by_name(df)

        self._default_fields = munchify(default_fields)

    @property
    def default_fields(self):
        """
        Returns a dict containing the friendly name of fields as keys and jira's proper field names as values.
        :return: dict
        """
        return self._default_fields

    def get_field_by_name(self, name):
        """
        Returns the true field name of a jira field based on its friendly name
        :param name: The friendly name of the field
        :return: A string with the true name of a field.
        """
        assert self.fields

        try:
            _name = name.lower()
            if _name.lower() in self.fields:
                return self.fields[_name]['id']

            else:
                return name

        except (KeyError, ValueError):
            return name

    def link_issues(self, link_type, inward, outward, comment=None):
        """
        Establishes a link in jira between two issues
        :param link_type: A string indicating the relationship from the inward to the outward
         (Example: "is part of this release")
        :param inward: Can be one of: Issue object, Issue dict, Issue key string
        :param outward: Can be one of: Issue object, Issue dict, Issue key string
        :param comment: None or a string with the comment associated with the link
        :return: No return value.
        """
        ""
        if isinstance(inward, dict):
            inward_key = inward['key']
        elif isinstance(inward, Issue):
            inward_key = inward.key
        elif isinstance(inward, str):
            inward_key = inward
        else:
            raise TypeError("'inward' parameter is not of a valid type")

        if isinstance(outward, dict):
            outward_key = outward['key']
        elif isinstance(outward, Issue):
            outward_key = outward.key
        elif isinstance(outward, str):
            outward_key = outward
        else:
            raise TypeError("'outward' parameter is not of a valid type")

        self.jira.create_issue_link(link_type, inward_key, outward_key,
                                    comment)

    def create_ticket(self, create_fields, update_fields=None, watchers=None):
        """
        Create the ticket with the required fields above.  The other keyword arguments can be used for other fields
           although the values must be in the correct format.
        :param update_fields:
        :param create_fields: All fields to include in the creation of the ticket. Keys include:
                project: A string with project key name (required)
                issuetype: A dictionary containing issuetype info (see Jira API docs) (required)
                summary: A string (required)
                description: A string
        :param update_fields: A dictionary containing reporter info  (see Jira API docs)
        :param watchers: A list of usernames that will be added to the watch list.

        :return: Return an Issue object or None if failed.
        """
        try:
            ticket = self.jira.create_issue(create_fields)

            if ticket:
                try:
                    # now update the remaining values (if any)
                    # we can't do this earlier because assignee and reporter can't be set during creation.
                    if update_fields and len(update_fields) > 0:
                        ticket.update(update_fields)
                except Exception as e:
                    self.logger.warning(
                        "Ticket was created but not updated due to exception: %s"
                        % e.message)

                try:
                    if watchers and isinstance(watchers, (list, tuple)):
                        [self.jira.add_watcher(ticket, w) for w in watchers]
                except Exception as e:
                    self.logger.warning(
                        "Unable to add watcher(s) due to exception: %s" %
                        e.message)

            return ticket

        except Exception as e:
            self.logger.error("Failed to create ticket: %s", e.message)
            return None
Пример #9
0
class jira_client(object):
    """Simple wrapper around jira api for build baron analyzer needs"""

    def __init__(self, jira_server, jira_user):

        self.jira = JIRA(
            options={'server': jira_server,
                     'verify': False},
            basic_auth=(jira_user, jira_client._get_password(jira_server, jira_user)),
            validate=True)

        # Since the web server may share this client among threads, use a lock since it unclear if
        # the JIRA client is thread-safe
        self._lock = threading.Lock()

    @staticmethod
    def _get_password(server, user):
        global keyring

        password = None

        if keyring:
            try:
                password = keyring.get_password(server, user)
            except:
                print("Failed to get password from keyring")
                keyring = None

        if password is not None:
            print("Using password from system keyring.")
        else:
            password = getpass.getpass("Jira Password:"******"Store password in system keyring? (y/N): ").strip()

                if answer == "y":
                    keyring.set_password(server, user, password)

        return password

    def query_duplicates_text(self, fields):
        search = "project in (bf, server, evg, build) AND (" + " or ".join(
            ['text~"%s"' % f for f in fields]) + ")"
        return search

    def search_issues(self, query, maxResults=50):
        with self._lock:
            results = self.jira.search_issues(
                query,
                fields=[
                    "id", "key", "status", "resolution", "summary", "created", "updated",
                    "assignee", "description"
                ],
                maxResults=maxResults)

        print("Found %d results" % len(results))

        return results

    def add_affected_version(self, bf_issue, affected_version_string):
        """
        Adds a new 'affectedVersion' to a BF, or does nothing if the version is already present in
        the issue's 'affectedVersions'.
        """
        affected_versions = bf_issue.fields.versions
        if affected_versions is None:
            affected_versions = []
        else:
            affected_versions = [{"name": v.name} for v in affected_versions]

        if affected_version_string.lower() == "master":
            affected_version_string = "3.6"

        if {"name": affected_version_string} in affected_versions:
            return

        affected_versions.append({"name": affected_version_string})
        try:
            bf_issue.update(fields={"versions": affected_versions})
        except JIRAError as e:
            print("Error updating issue's affected versions: " + str(e))

    def add_failing_task(self, bf_issue, failing_task_string):
        """
        Adds a new 'failing_task' to a BF, or does nothing if the version is already present in
        the issue's 'failing_task's.
        """
        failing_tasks = bf_issue.fields.customfield_12950
        if failing_tasks is None:
            failing_tasks = []

        if failing_task_string in failing_tasks:
            return

        failing_tasks.append(failing_task_string)

        try:
            bf_issue.update(fields={"customfield_12950": failing_tasks})
        except JIRAError as e:
            print("Error updating duplicate issue's failing_tasks: " + str(e))

    def add_affected_variant(self, bf_issue, variant_string):
        """
        Adds a new 'Buildvariant' to a BF, or does nothing if the version is already present in
        the issue's 'Buildvariant's, or if 'variant_string' is not a valid Buildvariant specifier.
        The latter case can happen if it is the name of a variant on an old branch, or if it is a
        new variant and we haven't updated JIRA yet.
        """
        affected_variants = bf_issue.fields.customfield_11454
        if affected_variants is None:
            affected_variants = []
        else:
            affected_variants = [{"value": v.value} for v in affected_variants]

        if {"value": variant_string} in affected_variants:
            return

        affected_variants.append({"value": variant_string})

        try:
            bf_issue.update(fields={"customfield_11454": affected_variants})
        except JIRAError as e:
            print("Error updating duplicate issue's Buildvariants: " + str(e))

    def add_github_backtrace_context(self, issue_string, backtrace):
        """
        Given a backtrace represented as follows, appends some nicely formatted previews of the code
        involved in the backtrace and adds them to the issue's description.

        A backtrace is a list of frames, specified as follows:
        {
          "github_url": "https://github.com/mongodb/mongo/blob/deadbeef/jstests/core/test.js#L42",
          "first_line_number": 37,
          "line_number": 42,
          "frame_number": 0,
          "file_path": "jstests/core/test.js",
          "file_name": "test.js",
          "lines": ["line 37", "line 38", ..., "line 47"]
        }
        """
        new_description_lines = [""]
        for frame in backtrace:
            frame_title = "Frame {frame_number}: [{path}:{line_number}|{url}]".format(
                frame_number=frame["frame_number"],
                path=frame["file_path"],
                line_number=frame["line_number"],
                url=frame["github_url"]
            )

            # raw text is 0-based, code lines are 1-based.
            first_line = frame["first_line_number"] + 1
            code_block_header = (
                "{code:js|title=%s|linenumbers=true|firstline=%d|highlight=%d}" %
                (frame["file_name"], first_line, frame["line_number"])
            )

            new_description_lines.append("")
            new_description_lines.append(frame_title)
            new_description_lines.append(code_block_header)
            new_description_lines.extend(frame["lines"])
            new_description_lines.append("{code}")

        self.append_to_issue_description(issue_string, new_description_lines)

    def add_fault_comment(self, issue_string, fault):
        """
        Adds a {noformat} block to the jira ticket's description summarizing the fault that we have
        extracted from the logs.
        """
        self.append_to_issue_description(
            issue_string,
            [
                "",
                "Extracted {fault_type}: ".format(fault_type=fault.category),
                "{noformat}",
                fault.context,
                "{noformat}"
            ]
        )

    def append_to_issue_description(self, issue_string, new_lines):
        """
        Adds the given text to the bottom of the given issue's description.

        If the ticket has already been tagged with the 'bot-analyzed' tag, no update occurs.
        """
        jira_issue = self.get_bfg_issue(issue_string)
        try:
            if "bot-analyzed" not in jira_issue.fields.labels:
                print("Updating issue description: \n" + "\n".join(new_lines))
                jira_issue.update(
                    description=jira_issue.fields.description + "\n".join(new_lines))
            else:
                print("Skipping issue update, since issue is already tagged with 'bot-analyzed'")
        except JIRAError as e:
            print("Error updating JIRA: " + str(e))


# jira.resolutions()
# <JIRA Resolution: name='Fixed', id='1'>
# <JIRA Resolution: name="Won't Fix", id='2'>
# <JIRA Resolution: name='Duplicate', id='3'>
# <JIRA Resolution: name='Incomplete', id='4'>
# <JIRA Resolution: name='Cannot Reproduce', id='5'>
# <JIRA Resolution: name='Works as Designed', id='6'>
# <JIRA Resolution: name='Gone away', id='7'>
# <JIRA Resolution: name='Community Answered', id='8'>
# <JIRA Resolution: name='Done', id='9'>

# Issue link Types
# <JIRA IssueLinkType: name='Backports', id='10420'>
# <JIRA IssueLinkType: name='Depends', id='10011'>
# <JIRA IssueLinkType: name='Documented', id='10320'>
# <JIRA IssueLinkType: name='Duplicate', id='10010'>
# <JIRA IssueLinkType: name='Gantt Dependency', id='10020'>
# <JIRA IssueLinkType: name='Gantt End to End', id='10423'>
# <JIRA IssueLinkType: name='Gantt End to Start', id='10421'>
# <JIRA IssueLinkType: name='Gantt Start to End', id='10424'>
# <JIRA IssueLinkType: name='Gantt Start to Start', id='10422'>
# <JIRA IssueLinkType: name='Related', id='10012'>
# <JIRA IssueLinkType: name='Tested', id='10220'>

    def close_as_duplicate(self, issue, duplicate_issue):

        with self._lock:
            src_issue = self.jira.issue(issue)
            dest_issue = self.jira.issue(duplicate_issue)

            # Add duplicate link
            self.jira.create_issue_link(
                type='Duplicate', inwardIssue=issue, outwardIssue=duplicate_issue)

            # Update affectsVersions, Buildvariants, etc.
            title_parsing_regex = re.compile("(Timed Out|Failures?):"
                                             " (?P<suite_name>.*?)"
                                             " on"
                                             " (?P<variant_prefix>[^\(\[]+)"
                                             "(?P<variant_suffix>"
                                             " (?:"
                                             "\(Clang 3\.7/libc\+\+\)|"
                                             "\(No Journal\)|"
                                             "\(inMemory\)|"
                                             "\(ephemeralForTest\)|"
                                             "\(Unoptimized\))(?: DEBUG)?)?"
                                             " (?:\("
                                             "(?P<test_names>(.*?(\.js|CheckReplDBHash)(, )?)*)"
                                             "\))? ?\[MongoDB \("
                                             "(?P<version>.*?)"
                                             "\) @ [0-9A-Za-z]+\]")

            parsed_title = title_parsing_regex.match(src_issue.fields.summary)
            if parsed_title is not None:
                # Update the failing variants.
                variant = parsed_title.group("variant_prefix").rstrip()
                if parsed_title.group("variant_suffix") is not None:
                    variant += parsed_title.group("variant_suffix").rstrip()

                self.add_affected_variant(dest_issue, variant)

                # Update the failing tasks.
                self.add_failing_task(dest_issue, parsed_title.group("suite_name"))

                # Update the affected versions.
                self.add_affected_version(dest_issue, parsed_title.group("version"))

            # Close - id 2
            # Duplicate issue is 3
            self.jira.transition_issue(src_issue, '2', resolution={'id': '3'})

            mongo_client.remove_issue(issue)

    def close_as_goneaway(self, issue):
        with self._lock:
            src_issue = self.jira.issue(issue)

            # Close - id 2
            # Gone away is 7
            self.jira.transition_issue(
                src_issue, '2', comment="Transient machine issue.", resolution={'id': '7'})

            mongo_client.remove_issue(issue)

    def get_bfg_issue(self, issue_number):
        if not issue_number.startswith("BFG-") and not issue_number.startswith("BF-"):
            issue_number = "BFG-" + issue_number
        with self._lock:
            src_issue = self.jira.issue(issue_number)
        return src_issue
Пример #10
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
Пример #11
0
def main():
    """creates tickets for a release task"""
    parser = argparse.ArgumentParser(
        description='Creates tickets for release certification')
    parser.add_argument('-u',
                        '--username',
                        help='jira username',
                        default='admin')
    parser.add_argument('-p',
                        '--password',
                        help='jira password',
                        default='admin')
    parser.add_argument('-c',
                        '--config',
                        help='path to config file',
                        default='./options.ini')
    parser.add_argument('-j',
                        '--jira',
                        help='url of jira server',
                        default='http://localhost:8080')

    args = parser.parse_args()

    jira_user = args.username
    jira_pass = args.password
    jira_server = args.jira
    config_file_path = args.config
    CONFIG.read(config_file_path)

    parent_ticket = config_map('JiraOptions')['parent_ticket']
    apprenda_version = config_map('VersionInfo')['to_version']
    jira_project = config_map('JiraOptions')['project']
    jira_issue_type = config_map('JiraOptions')['issue_type']
    jira = JIRA(jira_server, basic_auth=(jira_user, jira_pass))

    parent_issue = jira.issue(parent_ticket)
    ticket_list = []

    # create clean install tickets
    clean_strings = config_map('CleanInstallSection')
    for cloud in ['single', 'hybrid']:
        ticket_to_add = ticket.Ticket(jira_project, jira_issue_type)
        ticket_to_add.format_summary(clean_strings['summary'],
                                     apprenda_version, cloud)
        ticket_to_add.format_description(clean_strings['description'])
        ticket_list.append(ticket_to_add.__dict__)

    # create upgrade tickets
    from_versions = json.loads(config_map('VersionInfo')['from_versions'])
    upgrade_strings = config_map('UpgradeSection')

    # single cloud
    for version in from_versions:
        ticket_to_add = ticket.Ticket(jira_project, jira_issue_type)
        ticket_to_add.format_summary(upgrade_strings['summary'],
                                     apprenda_version, version, "single")
        ticket_to_add.format_description(upgrade_strings['description'])
        ticket_list.append(ticket_to_add.__dict__)

    # hybrid cloud
    for version in from_versions:
        ticket_to_add = ticket.Ticket(jira_project, jira_issue_type)
        ticket_to_add.format_summary(upgrade_strings['summary'],
                                     apprenda_version, version, "hybrid")
        ticket_to_add.format_description(upgrade_strings['description'])
        ticket_list.append(ticket_to_add.__dict__)

    # create testing tickets for other tasks
    for section in CONFIG.sections():
        if 'Ticket' in section:
            strings = config_map(section)
            ticket_to_add = ticket.Ticket(jira_project, jira_issue_type)
            ticket_to_add.format_summary(strings['summary'], apprenda_version)
            ticket_to_add.format_description(strings['description'])
            ticket_list.append(ticket_to_add.__dict__)

    print 'Created {0} tickets, now sending them to Jira'.format(
        len(ticket_list))
    # send issues to jira and create tickets and links
    issues = jira.create_issues(field_list=ticket_list)

    for item in issues:
        jira.create_issue_link(
            type="Task of Story",
            outwardIssue=item['issue'].key,
            inwardIssue=parent_issue.key,
        )

    print 'Finished linking issues, exiting.'
Пример #12
0
class JiraToolsAPI:
    def __init__(self, jira_server_link, username=None, password=None):
        """Initalizes the Jira API connector. If a username or password is not provided you will be prompted for it.

        args:
            jira_server_link (str): Link to the Jira server to touch API

        kwargs:
            username (str): Overwrites jira username prompt
            password (str): Overwrites jira password prompt

        return: None
        """
        self.jira_server_link = jira_server_link
        self.jira_options = {"server": self.jira_server_link}

        if username == None:
            username = input("Username: "******"Authenticated successfully with Jira with {self.username}")

    def create(self, data):
        """Create a single Jira ticket.

        args:
            data (dict): Fields required or needed to create the ticket.

        return (str): Ticket number / 'False' if fails
        """
        try:
            jira_ticket = self._JIRA.create_issue(fields=data)
            logging.info(
                f"Successfully created Jira issue '{jira_ticket.key}'")
            return jira_ticket.key

        except Exception as error:
            logging.debug(
                f"Failed to create Jira issue '{jira_ticket.key}'\n\n{error}\n\n"
            )
            return False

    def link(self, issue_from, issue_to, issue_link_name=None):
        """Link two issues together. Defaults to 'Relates' unless issue_link_name is specified.

        args:
            issue_from (str): Issue that will be linked from.
            issue_to (str): Issue that will be linked to.


        kwargs:
            issue_link_name (str): issue link name that should be applied.

        return (bool): Will return 'True' if it completed successfully.
        """
        try:
            self._JIRA.create_issue_link(issue_link_name, issue_from, issue_to)
            logging.info(
                f"Successfully created a '{issue_link_name}' link between '{issue_from}' and '{issue_to}'."
            )
            return True

        except Exception as error:
            logging.debug(
                f"Failed to create a link between '{issue_from}' and '{issue_to}'\n\n{error}\n\n"
            )
            return False

    def label(self, issue, labels):
        """Apply labels to a given issue.

        args:
            issue (str): Issue that labels will be applied to.
            labels (list): list of labels that should be applied to the issue.

        Return (bool): Will return 'True' if it completed successfully.
        """
        if type(labels) == list:
            try:
                issue_instance = self._JIRA.issue(issue)
                issue_instance.update(
                    fields={"labels": issue_instance.fields.labels + labels})
                logging.info(
                    f"Successfully added labels '{labels}' to '{issue}'")
                return True

            except Exception as error:
                logging.debug(
                    f"Failed to add labels '{labels}' to '{issue}'\n\n{error}\n\n"
                )
                return False

        else:
            raise ScriptError('A list must be passed to the labels argument')

    def comment(self, issue, comment):
        """Apply a comment to a given issue.

         args:
             issue (str): Issue that comment will be applied to.
             comment (str): comment that should be applied to the issue.

         return (bool): Will return 'True' if it completed successfully.
         """
        try:
            self._JIRA.add_comment(issue, comment)
            logging.info(
                f"Successfully added comment '{comment}' to '{issue}'")
            return True
        except Exception as error:
            logging.debug(
                f"Failed to add comment '{comment}' to '{issue}'\n\n{error}\n\n"
            )
            return False

    def log_work(self, issue, time_spent, comment=None):
        """Log work to a given issue.

        args:
            issue (str): Issue to log work.
            time_spent (str): Time that should be logged to the issue.

        kwargs:
            comment (str): Description of what this time represents.

        return (bool): Will return 'True' if it completed successfully.
        """
        try:
            if comment != None and type(comment) == str:
                self._JIRA.add_worklog(issue, time_spent, comment=comment)
            else:
                self._JIRA.add_worklog(issue, time_spent)
            logging.info(f"Successfully logged time to '{issue}'")
            return True

        except Exception as error:
            logging.info(
                f"Failed to log work to '{issue}' See debug logs for more.")
            logging.debug(f"\n{error}\n")
            return False

    def add_attachment(self, issue, attachment):
        """Attach file to Jira issue.

        args:
            issue (str): Issue name
            attachment (str): Location of file that should be attached.

        Return (bool): Will return 'True' if completed successfully
        """
        assert isinstance(issue, str)
        assert isinstance(attachment, str)

        try:
            self._JIRA.add_attachment(issue=issue, attachment=attachment)
            logging.info(f'Successfully attached document to "{issue}"')
            return True

        except Exception as error:
            logging.debug(
                f"Failed to attach document to '{issue}'\n\n{error}\n\n")
            return False

    def update_status(self,
                      id,
                      end_status,
                      transfer_statuses=[],
                      timeout_attempts=10):
        """Change issue to desired status.

        Due to the workflow features of Jira it might not be possible to transition
        directly to the wanted status, intermediary statuses might be required and
        this funcation allows for that using 'transfer_statuses'.

        args:
            id (str): Issue id for status update
            end_status (str): Name of status to update ticket to.

        kwargs:
            transfer_statuses (list): Ordered list of intermediary statuses
            timeout_attempts (num): Number of times before while loop times out.

        return (bool): Will return 'True' if completed successfully
        """
        while timeout_attempts != 0:
            transitions = self._JIRA.transitions(id)
            for transition in transitions:
                if transition['name'] == end_status:
                    jira_ticket = self._JIRA.transition_issue(
                        id, transition['id'])
                    logging.info(
                        f"Updated status of '{issue}' to '{end_status}'")
                    return True
                elif transition['name'] in transfer_statuses:
                    jira_ticket = self._JIRA.transition_issue(
                        id, transition['id'])
            timeout_attempts -= 1
        logging.debug(
            f"Failed to update status of '{id}' to end_status ({end_status})")
        return False
Пример #13
0
    'issuetype': {
        'name': 'Task'
    },
    'duedate':
    DueDate5week,
    'summary':
    'Тестирование {}. Ежедневный контроль производительности по реплеям и бенчмаркам.'
    .format(Current_Stable),
    'priority':
    Priority_high,
    'fixVersions': [{
        'name': '{}'.format(Current_Fix_Versions)
    }],
    'description':
    'Перечень тестов оформлен в виде саб-тасков: \n# Тестирование {}. Ежедневный контроль версии на'
    ' базе реплеев. \n# Тестирование {}. Ежедневный контроль производительности версии на базе'
    ' BenchmarkLocations.'.format(Current_Stable, Current_Stable),
    'components':
    Component_QA,
    'environment':
    Environment_of_creation_dev_cut_from_,
    'assignee': {
        'name': 'a_gorchakov'
    },
}
# #Cоздание ишью
Task1 = jira.create_issue(fields=TASK1_KONTROL)
## Связь двух ишью в эпик включен таск
jira.create_issue_link('is included in', Task1, EpicStory, None)
print("created Task1 " + Task1.key)
Пример #14
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"]}"'
            )
Пример #15
0
class jira_client(object):
    """Simple wrapper around jira api for build baron analyzer needs"""
    def __init__(self, jira_server, jira_user):

        self.jira = JIRA(options={
            'server': jira_server,
            'verify': False
        },
                         basic_auth=(jira_user,
                                     jira_client._get_password(
                                         jira_server, jira_user)),
                         validate=True)

        # Since the web server may share this client among threads, use a lock since it unclear if
        # the JIRA client is thread-safe
        self._lock = threading.Lock()

    @staticmethod
    def _get_password(server, user):
        global keyring

        password = None

        if keyring:
            try:
                password = keyring.get_password(server, user)
            except:
                print("Failed to get password from keyring")
                keyring = None

        if password is not None:
            print("Using password from system keyring.")
        else:
            password = getpass.getpass("Jira Password:"******"Store password in system keyring? (y/N): ").strip()

                if answer == "y":
                    keyring.set_password(server, user, password)

        return password

    def query_duplicates_text(self, fields):
        search = "project in (bf, server, evg, build) AND (" + " or ".join(
            ['text~"%s"' % f for f in fields]) + ")"
        return search

    def search_issues(self, query, maxResults=50):
        with self._lock:
            results = self.jira.search_issues(query,
                                              fields=[
                                                  "id", "key", "status",
                                                  "resolution", "summary",
                                                  "created", "updated",
                                                  "assignee", "description"
                                              ],
                                              maxResults=maxResults)

        print("Found %d results" % len(results))

        return results

    def add_affected_version(self, bf_issue, affected_version_string):
        """
        Adds a new 'affectedVersion' to a BF, or does nothing if the version is already present in
        the issue's 'affectedVersions'.
        """
        affected_versions = bf_issue.fields.versions
        if affected_versions is None:
            affected_versions = []
        else:
            affected_versions = [{"name": v.name} for v in affected_versions]
        if affected_version_string.lower() == "master":
            affected_version_string = "3.6"

        if {"name": affected_version_string} in affected_versions:
            return

        affected_versions.append({"name": affected_version_string})
        try:
            bf_issue.update(fields={"versions": affected_versions})
        except JIRAError as e:
            print("Error updating issue's affected versions: " + str(e))

    def add_failing_task(self, bf_issue, failing_task_string):
        """
        Adds a new 'failing_task' to a BF, or does nothing if the version is already present in
        the issue's 'failing_task's.
        """
        failing_tasks = bf_issue.fields.customfield_12950
        if failing_tasks is None:
            failing_tasks = []

        if failing_task_string in failing_tasks:
            return
        failing_tasks.append(failing_task_string)

        try:
            bf_issue.update(fields={"customfield_12950": failing_tasks})
        except JIRAError as e:
            print("Error updating duplicate issue's failing_tasks: " + str(e))

    def add_affected_variant(self, bf_issue, variant_string):
        """
        Adds a new 'Buildvariant' to a BF, or does nothing if the version is already present in
        the issue's 'Buildvariant's, or if 'variant_string' is not a valid Buildvariant specifier.
        The latter case can happen if it is the name of a variant on an old branch, or if it is a
        new variant and we haven't updated JIRA yet.
        """
        affected_variants = bf_issue.fields.customfield_11454
        if affected_variants is None:
            affected_variants = []
        else:
            affected_variants = [{"value": v.value} for v in affected_variants]

        if {"value": variant_string} in affected_variants:
            return

        affected_variants.append({"value": variant_string})

        try:
            bf_issue.update(fields={"customfield_11454": affected_variants})
        except JIRAError as e:
            print("Error updating duplicate issue's Buildvariants: " + str(e))

    def add_github_backtrace_context(self, issue_string, backtrace):
        """
        Given a backtrace represented as follows, appends some nicely formatted previews of the code
        involved in the backtrace and adds them to the issue's description.

        A backtrace is a list of frames, specified as follows:
        {
          "github_url": "https://github.com/mongodb/mongo/blob/deadbeef/jstests/core/test.js#L42",
          "first_line_number": 37,
          "line_number": 42,
          "frame_number": 0,
          "file_path": "jstests/core/test.js",
          "file_name": "test.js",
          "lines": ["line 37", "line 38", ..., "line 47"]
        }
        """
        new_description_lines = [""]
        for frame in backtrace:
            frame_title = "Frame {frame_number}: [{path}:{line_number}|{url}]".format(
                frame_number=frame["frame_number"],
                path=frame["file_path"],
                line_number=frame["line_number"],
                url=frame["github_url"])

            # raw text is 0-based, code lines are 1-based.
            first_line = frame["first_line_number"] + 1
            code_block_header = (
                "{code:js|title=%s|linenumbers=true|firstline=%d|highlight=%d}"
                % (frame["file_name"], first_line, frame["line_number"]))

            new_description_lines.append("")
            new_description_lines.append(frame_title)
            new_description_lines.append(code_block_header)
            new_description_lines.extend(frame["lines"])
            new_description_lines.append("{code}")

        self.append_to_issue_description(issue_string, new_description_lines)

    def add_fault_comment(self, issue_string, fault):
        """
        Adds a {noformat} block to the jira ticket's description summarizing the fault that we have
        extracted from the logs.
        """
        self.append_to_issue_description(issue_string, [
            "", "Extracted {fault_type}: ".format(fault_type=fault.category),
            "{noformat}", fault.context, "{noformat}"
        ])

    def append_to_issue_description(self, issue_string, new_lines):
        """
        Adds the given text to the bottom of the given issue's description.

        If the ticket has already been tagged with the 'bot-analyzed' tag, no update occurs.
        """
        jira_issue = self.get_bfg_issue(issue_string)
        try:
            if "bot-analyzed" not in jira_issue.fields.labels:
                print("Updating issue description: \n" + "\n".join(new_lines))
                jira_issue.update(description=jira_issue.fields.description +
                                  "\n".join(new_lines))
            else:
                print(
                    "Skipping issue update, since issue is already tagged with 'bot-analyzed'"
                )
        except JIRAError as e:
            print("Error updating JIRA: " + str(e))

# jira.resolutions()
# <JIRA Resolution: name='Fixed', id='1'>
# <JIRA Resolution: name="Won't Fix", id='2'>
# <JIRA Resolution: name='Duplicate', id='3'>
# <JIRA Resolution: name='Incomplete', id='4'>
# <JIRA Resolution: name='Cannot Reproduce', id='5'>
# <JIRA Resolution: name='Works as Designed', id='6'>
# <JIRA Resolution: name='Gone away', id='7'>
# <JIRA Resolution: name='Community Answered', id='8'>
# <JIRA Resolution: name='Done', id='9'>

# Issue link Types
# <JIRA IssueLinkType: name='Backports', id='10420'>
# <JIRA IssueLinkType: name='Depends', id='10011'>
# <JIRA IssueLinkType: name='Documented', id='10320'>
# <JIRA IssueLinkType: name='Duplicate', id='10010'>
# <JIRA IssueLinkType: name='Gantt Dependency', id='10020'>
# <JIRA IssueLinkType: name='Gantt End to End', id='10423'>
# <JIRA IssueLinkType: name='Gantt End to Start', id='10421'>
# <JIRA IssueLinkType: name='Gantt Start to End', id='10424'>
# <JIRA IssueLinkType: name='Gantt Start to Start', id='10422'>
# <JIRA IssueLinkType: name='Related', id='10012'>
# <JIRA IssueLinkType: name='Tested', id='10220'>

    def close_as_duplicate(self, issue, duplicate_issue):

        with self._lock:
            src_issue = self.jira.issue(issue)
            dest_issue = self.jira.issue(duplicate_issue)

            # Add duplicate link
            self.jira.create_issue_link(type='Duplicate',
                                        inwardIssue=issue,
                                        outwardIssue=duplicate_issue)

            # Update affectsVersions, Buildvariants, etc.
            title_parsing_regex = re.compile(
                "(Timed Out|Failures?):"
                " (?P<suite_name>.*?)"
                " on"
                " (?P<variant_prefix>[^\(\[]+)"
                "(?P<variant_suffix>"
                " (?:"
                "\(Clang 3\.7/libc\+\+\)|"
                "\(No Journal\)|"
                "\(inMemory\)|"
                "\(ephemeralForTest\)|"
                "\(Unoptimized\))(?: DEBUG)?)?"
                " (?:\("
                "(?P<test_names>(.*?(\.js|CheckReplDBHash)(, )?)*)"
                "\))? ?\[MongoDB \("
                "(?P<version>.*?)"
                "\) @ [0-9A-Za-z]+\]")

            parsed_title = title_parsing_regex.match(src_issue.fields.summary)
            if parsed_title is not None:
                # Update the failing variants.
                variant = parsed_title.group("variant_prefix").rstrip()
                if parsed_title.group("variant_suffix") is not None:
                    variant += parsed_title.group("variant_suffix").rstrip()

                self.add_affected_variant(dest_issue, variant)

                # Update the failing tasks.
                self.add_failing_task(dest_issue,
                                      parsed_title.group("suite_name"))

                # Update the affected versions.
                self.add_affected_version(dest_issue,
                                          parsed_title.group("version"))

            # Close - id 2
            # Duplicate issue is 3
            self.jira.transition_issue(src_issue, '2', resolution={'id': '3'})

    def close_as_goneaway(self, issue):
        with self._lock:
            src_issue = self.jira.issue(issue)

            # Close - id 2
            # Gone away is 7
            self.jira.transition_issue(src_issue,
                                       '2',
                                       comment="Transient machine issue.",
                                       resolution={'id': '7'})

    def get_bfg_issue(self, issue_number):
        if not issue_number.startswith("BFG-") and not issue_number.startswith(
                "BF-"):
            issue_number = "BFG-" + issue_number
        with self._lock:
            src_issue = self.jira.issue(issue_number)
        return src_issue
Пример #16
0
class JiraOperations(object):
    """ Base class for interaction with JIRA """
    def __init__(self, config):
        # do not print excess warnings
        urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
        # JIRA configuration from config.json/DDB
        self.config = config
        # JIRA url
        self.server = self.config.jira.server
        # JIRA established session
        self.session = None

        if self.config.jira.enabled:
            self.login_oauth()
        else:
            logging.debug("JIRA integration is disabled")

    @property
    def current_user(self):
        """ :return: JIRA user name, used for connection establishing """
        return self.session.current_user()

    def login_oauth(self):
        """
        Establish JIRA connection using oauth

        :return: boolean, if connection was successful.
        """
        if not self.config.jira.credentials:
            logging.error("Failed to login jira (empty credentials)")
            return False

        try:
            self.session = JIRA(options={
                'server': self.server,
                'verify': False
            },
                                oauth=self.config.jira.credentials["oauth"])
        except JIRAError:
            logging.exception(
                f"Failed to create oauth session to {self.server}")
            return False

        logging.debug(
            f'JIRA session to {self.server} created successfully (oauth)')
        return True

    def login_basic(self):
        """
        Establish JIRA connection using basic authentication

        :return: boolean, if connection was successful.
        """
        if not self.config.jira.credentials:
            logging.error("Failed to login jira (empty credentials)")
            return False

        username = self.config.jira.credentials["basic"]["username"]
        password = self.config.jira.credentials["basic"]["password"]
        options = {'server': self.server, 'verify': False}

        try:
            self.session = JIRA(options, basic_auth=(username, password))
        except Exception:
            logging.exception(
                f"Failed to create basic session to {self.server}")
            return False

        logging.debug(
            f'JIRA session to {self.server} created successfully (basic)')
        return True

    def ticket_url(self, ticket_id):
        """ :return: URL to `ticket_id` """
        return f"{self.server}/browse/{ticket_id}"

    def ticket_assignee(self, ticket_id):
        """
        :param ticket_id: JIRA ticket
        :return: name of current assignee for ticket
        """
        ticket = self.session.issue(ticket_id)
        return ticket.fields.assignee.name

    def find_valid_assignee(self, project, assignees):
        """
        Check what record from given list of possible assignees can be used as assignee for given project.

        :param project: name of Jira project to perform check against
        :param assignees: list of possible assignees
        :return:
        """
        for assignee in assignees:
            if assignee is None:
                continue

            try:
                users = self.session.search_assignable_users_for_projects(
                    assignee, project)
            except Exception:
                continue

            # check only exact matches
            if len(users) == 1:
                return users[0].name
        return None

    def create_ticket(self, issue_data):
        """
        Create a JIRA ticket

        :param issue_data: a dict containing field names and the values to use
        """
        resp = self.session.create_issue(fields=issue_data)
        logging.debug(f"Created jira ticket {self.ticket_url(resp.key)}")
        return resp.key

    def create_issue_link(self, inward_issue, outward_issue):
        """
        Linking JIRA tickets with 'relates to' link

        :return: boolean, if linking was successful
        """
        if not (inward_issue or outward_issue):
            return False

        try:
            # JIRA comes with default types of links:
            #  1) relates to / relates to,
            #  2) duplicates / is duplicated by,
            #  3) blocks / is blocked by
            #  4) clones / is cloned by
            link_type = "relates to"
            self.session.create_issue_link(type=link_type,
                                           inwardIssue=inward_issue,
                                           outwardIssue=outward_issue)
        except Exception:
            logging.exception(
                f"Failed to create issue link {inward_issue} -> {outward_issue}"
            )
            return False

        logging.debug(f"Created issue link {inward_issue} -> {outward_issue}")
        return True

    def assign_user(self, ticket_id, assinee_name):
        """
        Assign `ticket_id` to `assinee_name`.

        :return: boolean, if assigning was successful
        """
        if not (ticket_id or assinee_name):
            return False

        try:
            issue = self.session.issue(ticket_id)
            issue.update(assignee={'name': assinee_name})
        except Exception:
            logging.exception(
                f"Failed to assign {ticket_id} to {assinee_name}")
            return False

        logging.debug(f"Assigned {ticket_id} to {assinee_name}")
        return True

    def add_label(self, ticket_id, label):
        """
                add label to `ticket_id`.

                :return: boolean, if label update was successful
                """
        if not (ticket_id and label):
            return False

        try:
            issue = self.session.issue(ticket_id)
            issue.fields.labels.append(label)
            issue.update(fields={"labels": issue.fields.labels})

        except Exception:
            logging.exception(f"Failed to add {label} to {ticket_id}")
            return False

        logging.debug(f"Added label {label} to {ticket_id}")
        return True

    def update_ticket(self, ticket_id, updated_issue_data):
        """
        Update JIRA ticket fields as in self.create_ticket(), but for existing ticket

        :param ticket_id: ticket Id to update
        :param updated_issue_data: a dict containing field names and the values to use

        :return: boolean, if updating was successful
        """
        try:
            issue = self.session.issue(ticket_id)
            issue.update(updated_issue_data)
        except Exception:
            logging.exception(f"Failed to update {ticket_id}")
            return False

        logging.debug(f"Updated {ticket_id}")
        return True

    def add_comment(self, ticket_id, comment):
        """
        Add comment to JIRA ticket

        :param ticket_id: ticket Id to add comment to
        :param comment: comment text

        :return: boolean, if operation was successful
        """
        if ticket_id and comment:
            try:
                self.session.add_comment(ticket_id, comment)
            except Exception:
                logging.exception(f"Failed to add comment to {ticket_id}")
                return False
        return True

    def add_watcher(self, ticket_id, user):
        """
        Adding jira ticket watcher.
        
        :param ticket_id: jira ticket id 
        :param user: watcher user id
        :return: nothing
        """

        self.session.add_watcher(ticket_id, user)

    def close_issue(self, ticket_id):
        """
        Transition of ticket to `Closed` state. It checks if issue can be transitioned to `Closed` state.

        :param ticket_id: ticket Id to close

        :return: nothing
        """
        if not ticket_id:
            return

        issue = self.session.issue(ticket_id)
        if issue.fields.status.name == "Closed":
            logging.debug(f"{ticket_id} is already closed")
            return

        for transition in self.session.transitions(issue):
            if transition['name'] == 'Close Issue':
                self.session.transition_issue(ticket_id, transition['id'])
                logging.debug(f"Closed {ticket_id}")
                break
        else:
            logging.error(f"{self.ticket_url(ticket_id)} can't be closed")
            return

    def resolve_issue(self, ticket_id):
        """
        Transition of ticket to `Resolved` state. It checks if issue can be transitioned to `Resolved` state.

        :param ticket_id: ticket Id to resolve

        :return: nothing
        """
        issue = self.session.issue(ticket_id)
        if issue.fields.status.name == "Resolved":
            logging.debug(f"{ticket_id} is already resolved")
            return

        for transition in self.session.transitions(issue):
            if transition['name'] == 'Resolve Issue':
                self.session.transition_issue(ticket_id, transition['id'])
                logging.debug(f"Resolved {ticket_id}")
                break
        else:
            logging.error(f"{self.ticket_url(ticket_id)} can't be resolved")
            return

    def reopen_issue(self, ticket_id):
        """
        Transition of ticket to `Reopen Issue` state. It checks if issue can be transitioned to `Reopen Issue` state.

        :param ticket_id: ticket Id to reopen

        :return: nothing
        """
        issue = self.session.issue(ticket_id)
        if issue.fields.status.name in ["Open", "Reopened"]:
            logging.debug(f"{ticket_id} is already opened")
            return

        for transition in self.session.transitions(issue):
            if transition['name'] == 'Reopen Issue':
                self.session.transition_issue(ticket_id, transition['id'])
                logging.debug(f"Reopened {ticket_id}")
                break
        else:
            logging.error(f"{self.ticket_url(ticket_id)} can't be reopened")
            return

    def add_attachment(self, ticket_id, filename, text):
        """
        Add text as attachment with filename to JIRA ticket

        :param ticket_id: ticket Id to add attachment to
        :param filename: label for attachment
        :param text: attachment text

        :return: attachment object
        """
        attachment = io.StringIO(text)
        filename = filename.replace(':', '-')
        return self.session.add_attachment(issue=ticket_id,
                                           attachment=attachment,
                                           filename=filename)

    @staticmethod
    def build_tags_table(tags):
        """
        Build JIRA table from AWS tags dictionary

        :param tags: dict with tags

        :return: str with JIRA table
        """
        if not tags:
            return ""

        desc = f"*Tags*:\n"
        desc += f"||Key||Value||\n"
        for key, value in tags.items():
            desc += f"|{key}|{empty_converter(value)}|\n"
        return desc
Пример #17
0
class jira_creator:
    def __init__(self,
                 jira_href,
                 login,
                 password,
                 file,
                 author=None):  #инициализация класса, получение переменных
        self.jira_href = jira_href
        self.login = login
        self.password = password
        self.file = file
        self.jira = None
        self.effect = 'Выполнение'
        self.target = 'Учет'
        self.author = author

    def __duedate_confirm(self, str_date):
        try:
            str_date = re.search('\d{4}-\d{2}-\d{2}', str(str_date))[0]
            return str_date
        except:
            return None

    def __permiss_list(
        self, permiss_str
    ):  #вспомогательная функция для подготовки списка массивов прав доступа
        dicts_list = []
        try:
            for i in permiss_str.split(','):
                dicts_list.append({'name': i})
            return dicts_list
        except:
            dicts_list.append({'name': ''})
            return dicts_list

    def __assert_DF(self, DF, manager_ia,
                    manager_project):  #проверка данных в датафрейме
        if type(manager_ia) != str and manager_ia.find(',') != -1:
            raise Exception(
                'Exception! In "manager_ia" column finded ",". Maybe many users in cell'
            )
        if type(manager_project) != str and manager_project.find(',') != -1:
            raise Exception(
                'Exception! In "manager_project" column finded ",". Maybe many users in cell'
            )
        if True in DF.assignee.str.contains(',').values:
            raise Exception(
                'Exception! In "assignee" column finded ",". Maybe many users in cell'
            )
        if False in (DF.customfield_11630 > DF.customfield_11610).values:
            raise Exception('Exception! date of end < startdate')
        if False in (DF.duedate > DF.customfield_11610).values:
            raise Exception('Exception! duedate < startdate')
        if True in (DF.summary.apply(len) > 150).values:
            raise Exception(
                'Exception! More than 150 characters in the "summary" column')

    def __prepare_df(
            self, file
    ):  #функция подготовки полученной таблицы к нужному для jira виду
        DF = pd.read_excel(file)
        list_decepen = []
        DF = DF.rename({'Проект':'project', 'Тип_задачи':'issuetype', 'Заголовок':'summary', 'Описание':'description',\
                        'Дата_Начала':'customfield_11610', 'Планируемая_дата_выполнения':'customfield_11630', 'Срок_исполнения':'duedate',\
                        'Метка':'labels', 'Менеджер_ВА':'customfield_21400', 'Менеджер_проекта':'customfield_11622',\
                        'Доступ_к_задаче':'customfield_10909', 'Ответственный':'assignee'}, axis=1)
        DF['customfield_11627'] = self.effect
        DF['customfield_11651'] = self.target
        DF.summary = DF.summary.apply(
            lambda x: x.replace('"', '').replace('\\', '/'))
        DF.project = DF.project.apply(lambda x: {'key': x})
        DF.issuetype = DF.issuetype.apply(lambda x: {'name': x})
        DF.labels = DF.labels.apply(lambda x: x.split(','))
        DF.customfield_10909 = DF.customfield_10909.apply(self.__permiss_list)
        DF.customfield_11610 = DF.customfield_11610.apply(
            lambda x: re.search('\d{4}-\d{2}-\d{2}', str(x))[0])
        DF.customfield_11630 = DF.customfield_11630.apply(
            lambda x: re.search('\d{4}-\d{2}-\d{2}', str(x))[0])
        DF.duedate = DF.duedate.apply(self.__duedate_confirm)
        list_df = DF.issuetype.tolist()
        for j, i in enumerate(list_df):
            if i['name'] == 'Проект':
                list_decepen.append(j)
            elif i['name'] == 'Веха':
                k = j
                if list_df[j - 1] == 'Задача':
                    k = j
                list_decepen.append(k)
            elif i['name'] == 'Задача':
                list_decepen.append(k)
        DF['hierarchy'] = list_decepen
        list_unique = list(set(list_decepen))
        manager_ia = DF.customfield_21400[0]
        manager_project = DF.customfield_11622[0]
        DF = DF.drop(['customfield_21400', 'customfield_11622'], axis=1)
        self.__assert_DF(DF, manager_ia, manager_project)
        return DF, list_unique, manager_ia, manager_project

    def __assign_update(
            self, key, assignee, manager_ia,
            manager_project):  #функция для назначения задачи на ответственного
        issue = self.jira.issue(key)
        issue.update(
            fields={
                'assignee': {
                    'name': assignee
                },
                'customfield_21400': {
                    'name': manager_ia
                },
                'customfield_11622': {
                    'name': manager_project
                }
            })
        if self.author:
            try:
                issue.update(fields={'reporter': {'name': self.author}})
            except:
                logging.warning('cannot change author in {}'.format(key))

    #def __project_mark_cell_to (self, key, project_id, mark_id):  #маркировка проектом и вехой
    #    issue = self.jira.issue(key)
    #    issue.update(fields={'customfield_22700':[str(project_id)]})
    #    issue.update(fields={'customfield_22701':[str(mark_id)]})

    def __create_project_issue(self, dict_project_issue):  #создание проекта
        project_issue = self.jira.create_issue(fields=dict_project_issue)
        id_project = project_issue.id
        key_project = project_issue.key
        logging.info('{} is create'.format(key_project))
        return key_project, id_project

    def __create_mark_issue(self, dict_mark_issue):  #создание вехи
        mark_issue = self.jira.create_issue(fields=dict_mark_issue)
        id_mark_issue = mark_issue.id
        key_mark_issue = mark_issue.key
        logging.info('{} is create'.format(key_mark_issue))
        return key_mark_issue, id_mark_issue

    def __create_task_issue(self, dict_task_issue):  #создание задачи
        task_issue = self.jira.create_issue(fields=dict_task_issue)
        key_task_issue = task_issue.key
        id_task_issue = task_issue.id
        logging.info('{} is create'.format(key_task_issue))
        return key_task_issue, id_task_issue

    def __jira_auth(self):  #авторизация
        options = {"server": self.jira_href}
        self.jira = JIRA(options, basic_auth=(self.login, self.password))
        return self.jira

    def __search_double_issue(
            self, dict_issue,
            assignee):  #функция проверки существования задачи
        logging.info('{} :check for existence '.format(dict_issue['summary']))
        search_by_description = self.jira.search_issues(
            'project=IAIT AND summary ~"{}"'.format(dict_issue['summary']))
        if search_by_description:
            if search_by_description.fields.summary == dict_issue[
                    'summary'] and search_by_description.fields.assignee.name == assignee:
                logging.info('{} :already exist'.format(dict_issue['summary']))
                return search_by_description[0].key, search_by_description[
                    0].id, False
        else:
            if dict_issue['issuetype']['name'] == 'Проект':
                key, id = self.__create_project_issue(dict_issue)
                return key, id, True
            elif dict_issue['issuetype']['name'] == 'Веха':
                key, id = self.__create_mark_issue(dict_issue)
                return key, id, True
            elif dict_issue['issuetype']['name'] == 'Задача':
                key, id = self.__create_task_issue(dict_issue)
                return key, id, True
            else:
                raise Exception(dict_issue['issuetype']['name'],
                                ' is unsupport name of issue')

    def IAIT_create_project(
            self):  #основное тело для вызова функций и преобразования данных
        if self.jira == None:
            self.jira = self.__jira_auth()
        DF, list_unique, manager_ia, manager_project = self.__prepare_df(
            self.file)
        DF_project = DF[DF.hierarchy == list_unique[0]].drop(
            ['hierarchy', 'duedate'], axis=1)
        dict_project = DF_project.to_dict('r')[0]
        assignee_issue = dict_project.pop('assignee')
        project_key, project_id, project_not_exist = self.__search_double_issue(
            dict_project, assignee_issue)
        if project_not_exist:
            self.__assign_update(project_key, assignee_issue, manager_ia,
                                 manager_project)
        for i in list_unique[1:]:
            DF_marks = DF[DF.hierarchy == i]
            DF_marks["customfield_22700"] = DF_marks.summary.apply(
                lambda x: [project_id])
            dict_marks = DF_marks.iloc[:1, :].drop([
                'customfield_11627', 'customfield_11651', 'hierarchy',
                'duedate'
            ],
                                                   axis=1).to_dict('r')[0]
            assignee_issue = dict_marks.pop('assignee')
            mark_key, mark_id, mark_not_exist = self.__search_double_issue(
                dict_marks, assignee_issue)
            if mark_not_exist:
                self.__assign_update(mark_key, assignee_issue, manager_ia,
                                     manager_project)
                self.jira.create_issue_link(type="Иерархия задач",
                                            inwardIssue=project_key,
                                            outwardIssue=mark_key)
                #self.__project_mark_cell_to(mark_key, project_id, mark_id)
                logging.info('{} is create link to {}'.format(
                    mark_key, project_key))
            DF_tasks = DF_marks.iloc[1:, :].drop(
                ['customfield_11627', 'customfield_11651', 'hierarchy'],
                axis=1)
            DF_tasks["customfield_22701"] = DF_tasks.summary.apply(
                lambda x: [mark_id])
            DF_tasks["customfield_22700"] = DF_tasks.summary.apply(
                lambda x: [project_id])
            dict_tasks = DF_tasks.to_dict('r')
            for task in dict_tasks:
                assignee_issue = task.pop('assignee')
                task_key, task_id, task_not_exist = self.__search_double_issue(
                    task, assignee_issue)
                if task_not_exist:
                    self.__assign_update(task_key, assignee_issue, manager_ia,
                                         manager_project)
                    logging.info('{} is create link to {}'.format(
                        task_key, mark_key))
Пример #18
0
class CoachesHelpDesk:
    def __init__(self, domain='jira.fiware.org'):
        self.base_url = 'https://{}'.format(domain)
        self.user = JIRA_USER
        self.password = JIRA_PASSWORD
        options = {'server': self.base_url, 'verify': False}
        self.jira = JIRA(options=options,
                         basic_auth=(self.user, self.password))
        self.n_assignment = 0
        self.n_clones = 0
        self.n_renamed = 0

    def assign_request(self):
        query = 'project = HELC AND issuetype = extRequest AND component = EMPTY'
        requests = sorted(self.jira.search_issues(query, maxResults=False),
                          key=lambda item: item.key)
        for request in requests:
            summary = request.fields.summary
            if re.search(r'\[SPAM\]', summary):
                request.update(fields={'components': [{'name': 'SPAM'}]})
                continue
            match = re.search(r'\[[^\]]+?\]', summary)
            if match:
                accelerator = match.group(0)[1:-1]
                if accelerator in accelerators_dict:
                    components = {'name': accelerators_dict[accelerator]}
                    # request.update(fields={'components':[components]}, assignee={'name': '-1'})
                    request.update(fields={'components': [components]})

                    if not request.fields.assignee:
                        self.jira.assign_issue(request, '-1')

                    self.n_assignment += 1
                    logging.info('updated request {}, accelerator= {}'.format(
                        request, accelerator))

    def clone_to_main(self):
        self._clone_tech()
        self._clone_lab()

    def _clone_tech(self):
        query = 'project = HELC AND issuetype = extRequest AND component = _TECH_ AND not assignee = EMPTY'
        requests = sorted(self.jira.search_issues(query, maxResults=False),
                          key=lambda item: item.key)

        for request in requests:
            fields = {
                'project': {
                    'key': 'HELP'
                },
                'components': [{
                    'name': 'FIWARE-TECH-HELP'
                }],
                'summary': request.fields.summary,
                'description': request.fields.description,
                'issuetype': {
                    'name': request.fields.issuetype.name
                },
                'priority': {
                    'name': request.fields.priority.name
                },
                'labels': request.fields.labels,
                'assignee': {
                    'name': None
                },
                'reporter': {
                    'name': request.fields.reporter.name
                }
            }
            new_issue = self.jira.create_issue(fields=fields)

            self.jira.create_issue_link('relates to', new_issue, request)
            self.jira.add_watcher(new_issue, request.fields.assignee.name)
            self.jira.remove_watcher(new_issue, self.user)

            components = [{
                'name': comp.name
            } for comp in request.fields.components if comp.name != '_TECH_']
            request.update(fields={'components': components})

            # self.jira.add_watcher(new_issue, request.fields.assignee.name)
            logging.info('CREATED TECH ISSUE: {} from {}'.format(
                new_issue, request))
            self.n_clones += 1

    def _clone_lab(self):
        query = 'project = HELC AND issuetype = extRequest AND component = _LAB_ AND not assignee = EMPTY'
        requests = sorted(self.jira.search_issues(query, maxResults=False),
                          key=lambda item: item.key)

        for request in requests:
            fields = {
                'project': {
                    'key': 'HELP'
                },
                'components': [{
                    'name': 'FIWARE-LAB-HELP'
                }],
                'summary': request.fields.summary,
                'description': request.fields.description,
                'issuetype': {
                    'name': request.fields.issuetype.name
                },
                'priority': {
                    'name': request.fields.priority.name
                },
                'labels': request.fields.labels,
                'assignee': {
                    'name': None
                },
                'reporter': {
                    'name': request.fields.reporter.name
                }
            }
            new_issue = self.jira.create_issue(fields=fields)

            self.jira.create_issue_link('relates to', new_issue, request)
            self.jira.add_watcher(new_issue, request.fields.assignee.name)
            self.jira.remove_watcher(new_issue, self.user)

            components = [{
                'name': comp.name
            } for comp in request.fields.components if comp.name != '_LAB_']
            request.update(fields={'components': components})

            logging.info('CREATED LAB ISSUE: {} from {}'.format(
                new_issue, request))
            self.n_clones += 1

    def naming(self):
        query = 'project = HELC AND issuetype = extRequest AND status = Closed and updated >= -1d'
        requests = sorted(self.jira.search_issues(query, maxResults=False),
                          key=lambda item: item.key)
        for request in requests:
            component = request.fields.components[0].name
            summary = request.fields.summary

            if re.match(r'FIWARE.Request.Coach.{}'.format(component), summary):
                continue

            summary = re.sub(r'\[[^\]]+?\]', '', summary)
            summary = 'FIWARE.Request.Coach.{}.{}'.format(
                component, summary.strip())
            request.update(summary=summary)
            logging.info('{} {} {} {}'.format(request, request.fields.status,
                                              component, summary))
            self.n_renamed += 1