Package tlib :: Package base :: Module JiraModerator |
[hide private]
[frames] | no frames]

# Source Code for Module tlib.base.JiraModerator

      1  # noinspection PyPackageRequirements
      2  import jira
      3  # noinspection PyPackageRequirements
      4  from jira.client import JIRA
      5  # noinspection PyPackageRequirements
      6  from jira.exceptions import JIRAError
      7  from requests import ConnectionError, HTTPError
      8
      9
     10 -class JiraModerator(object):
     11      """
     12      Helper class to connect and manipulate the data in Jira
     13      """
     14
     15      client = None
     16      _clientOptions = {'server': ""}    #: connection options for Jira
     17      _username = ""                              #: Automation User username (QA Lead access to Jira)
     18      _password = ""                                 #: Automation User password
     19      logger = None                                           #: logger to send loggin information to.
     20      project = None
     21
     22 -    def __init__(self, logger, server, username , password):
     23          """
     24          Constructor for class
     25
     26          @param logger: instance of a logging object configured in testing project
     27          @type logger: Logger
     28          """
     29          self.logger = logger
     30
     31          self._clientOptions['server'] = server
     32          self._username = username
     33          self._password = password
     34
     35 -    def connect(self):
     36          """
     37          Establishes a connection to our YPG Jira instance and assignes it to self.client
     38
     39          @return: True if we are connected to Jira and False if we are not
     40          @rtype: bool
     41          """
     42
     43          try:
     44              self.client = JIRA(self._clientOptions, basic_auth=(self._username, self._password))
     45              self.client.session()
     46              success = True
     47
     48          except ConnectionError, e:
     49              self.logger.error("Error Connection to Jira :")
     50              self.logger.error(e)
     51              self.client = None
     52              success = False
     53          except HTTPError, e:
     54              self.logger.error("Error Connection to Jira :")
     55              self.logger.error(e)
     56              self.client = None
     57              success = False
     58
     59          return success
     60
     61 -    def search_for_issue(self, query):
     62          """
     63          Returns a list of issues that were returned by Jira given the query you specified
     64
     65          @param query: A string representation of a JQL query.  anything you can enter in Jira advanced search can be
     66          entered here.
     67          @type query:: str
     68
     69          @return: a list of jira issues
     70          @rtype: ResultList
     71          """
     72          result_list = None
     73          try:
     74              result_list = self.client.search_issues(query)
     75
     76          except JIRAError, e:
     77              self.logger.error("Could not search what you are looking for because " + str(e))
     78
     79          if len(result_list) < 1:
     80              issues = None
     81          elif len(result_list) > 1:
     82              self.logger.warn(
     83                  '''Jira found more than one issue with the search %s . You may want to manually verify
     84                          the automated process updated the correct issue." % query)''')
     85              issues = result_list[0]
     86          else:
     87              issues = result_list[0]
     88          return issues
     89
     90 -    def confirm_version(self, proj_key, build_version):
     91          """
     92          Confirms that the project version supplied actually exists in Jira for the specified project
     93
     94          @param proj_key: the Jira project acronym
     95          @type proj_key: str
     96          @param build_version: the version of the application you are testing
     97          @type build_version: str
     98
     99          @return: True if the version was found in Jira.  False if the version was not found
    100          @rtype: bool
    101          """
    102
    103          try:
    104              # get all the versions for the specified project from Jira
    105              project = self.client.project(proj_key)
    106              proj_versions = project.versions
    107
    108              # search for the version we are testing.  Is it in Jira?
    109              for version in proj_versions:
    110
    111                  if str(version.name) == str(build_version):
    112                      self.logger.debug("Matched the specidied buildVersion runtime parameter to version in Jira")
    113                      if not version.released and not version.archived:
    114                          self.logger.debug(
    115                              "We are going to start to test build version " + version.name + " for project " + proj_key)
    116                          return True
    117                      else:
    118                          self.logger.warn(
    119                              '''The buildVersion you are searching for has been released or archived in Jira
    120                                 and is not a valid testable build version''')
    121                          return False
    122          except JIRAError:
    123              self.logger.error(
    124                  "Could not retrieve the projects versions.  Check that the project exists or that Jira is not down")
    125              return False
    126
    127 -    def get_required_fields(self, project_key, issue_type=None):
    128          """
    129          Fetches a list of fields that require values to open a new issue in a specified project
    130
    131          @param project_key: The Jira project acronym for the project you are querying
    132          @type project_key: str
    133          @param issue_type: Optional issue type Names.  Single name or coma delimited string
    134          @type issue_type: str
    135
    136          @return: a dictionary containing the required fields and None values for each.  Returns empty dict if
    137          search failed.
    138          @rtype: dict
    139          """
    140
    141          req_fields = {}
    142
    143          try:
    144              # Get a list of fields that are required to supply values for so that an issue can be created
    145              meta = self.client.createmeta(projectKeys=project_key,
    146                                            issuetypeNames=issue_type,
    147                                            expand='projects.issuetypes.fields')
    148
    149              fields = meta['projects'][0]['issuetypes'][0]['fields']
    150
    151              for field in fields:
    152                  if fields[field]['required']:
    153                      req_fields[field] = None
    154
    155          except JIRAError, e:
    156              self.logger.error("Could not get required fields for Jira project " + project_key + " because " + str(e))
    157          except IndexError, e:
    158              self.logger.error("Could not get required fields for Jira project " + project_key + " because " + str(e))
    159
    160          return req_fields
    161
    162 -    def create_issue(self, data):
    163          """
    164          Creates an issue in Jira with the dictionary of data supplied.  Be sure that data contains all required
    165          fields before using this.
    166
    167          @param data: dictionary of required fields and valid values
    168          @type data: dict
    169
    170          @return: returns True if issues was created and False if there was a failure
    171          @rtype: bool
    172          """
    173
    174          try:
    175              self.client.create_issue(fields=data)
    176              success = True
    177
    178          except JIRAError, e:
    179              success = False
    180              self.logger.error("Issue was not created :" + str(e))
    181
    182          return success
    183
    184 -    def reopen_bug(self, issue, proj_key, version):
    185          """
    186          Transitions a specified jira Bug from any resolved state back to In Review and assigns it to the
    187          project lead with comments
    188
    189          @param issue: an issue object that came from Jira.  Use searchForIssue first before reopening issues
    190          @type issue: issue
    191          @param proj_key: the Jira project acronym from Jira
    192          @type proj_key: str
    193          @param version: the build number currently under test where the bug was rediscovered
    194          @type version: str
    195
    196          @return: returns False if we could not reopen issue and True if we could
    197          @rtype: bool
    198          """
    199          cur_state = issue.fields.status.name
    200
    201          transitions = self.client.transitions(issue)
    202
    203          project = self.client.project(proj_key)
    204          proj_lead = project.lead.name
    205
    206          version = version
    207
    208          comment = "This issue has reoccured in the latest version %s" % version
    209
    210          try:
    211              if cur_state == "Closed":
    212                  self.client.transition_issue(issue, self.get_transition_id(transitions, "Re-Open"),
    213                                               assignee={'name': proj_lead})
    214                  self.client.add_comment(issue, comment)
    215              elif cur_state == "Ready for QA":
    216                  self.client.transition_issue(issue, self.get_transition_id(transitions, "Back to In Development"),
    217                                               assignee={'name': proj_lead})
    218                  self.client.add_comment(issue, comment)
    219              elif cur_state == "In Testing":
    220                  self.client.transition_issue(issue, self.get_transition_id(transitions, "Fail"),
    221                                               assignee={'name': proj_lead})
    222                  self.client.add_comment(issue, comment)
    223              elif cur_state == "Ready to Deploy":
    224                  self.client.transition_issue(issue, self.get_transition_id(transitions, "Back to In Testing"))
    225                  transitions = self.client.transitions(issue)
    226                  self.client.transition_issue(issue, self.get_transition_id(transitions, "Fail"),
    227                                               assignee={'name': proj_lead})
    228                  self.client.add_comment(issue, comment)
    229          except IndexError:
    230              self.logger.error("Could not find a transition to reopen the issue '" + issue.key + "'")
    231              return False
    232          except JIRAError, e:
    233              self.logger.error("Jira returned error when modifying issue '" + issue.key + "' because " + str(e))
    234              return False
    235
    236          return True
    237
    238      #noinspection PyMethodMayBeStatic
    239 -    def get_transition_id(self, trans_dict, trans_name):
    240          """
    241          Fetch the id for a transition's name
    242
    243          @param trans_dict: a dictionary of transitions fetched from Jira for a given issue
    244          @type trans_dict: dict
    245          @param trans_dict: name of the Jira transition you would like the id for
    246          @type trans_name: str
    247
    248          @return: a numeric id associtated to the transition name
    249          @rtype: str
    250          """
    251
    252          id_dict = [element['id'] for element in trans_dict if element['name'] == trans_name]
    253          return id_dict[0]
    254
    255      #noinspection PyMethodMayBeStatic
    256 -    def prepare_issue_data(self, req_fields, test_id, project, summary, description, component, severity, version):
    257          """
    258          Constructs a properly formatted dictionary of data to supply to Jira for opening an issue.  Creates a bug
    259          in the specified project After construction, it will validate the dictionary by checking if all required
    260          fields are filled
    261
    262          @param req_fields: dictionary of jira project required fields.  Construct the dict with getRequiredFields
    263          @type req_fields: dict
    264          @param test_id: the name of the test cases found in the test case's decoreator in python or the name in Spiratest
    265          @type test_id: str
    266          @param project: the Jira project acronym for the project you wish to open a bug inspect
    267          @type project: str
    268          @param summary: the summary of the bug you wish to open
    269          @type summary: str
    270          @param description: the description of the bug you wish to open
    271          @type description: str
    272          @param component: the component of the bug you wish to open
    273          @type component: str
    274          @param severity: the severity of the bug you wish to open
    275          @type severity: str
    276          @param version: the affected version of the bug you wish to open
    277          @type version: str
    278
    279          @return: if the dictionary properly complies to all the required fields.
    280          Returns emtpy dict if it does not.
    281          @rtype: dict
    282          """
    283          desc = '''This issue was created by YPG automated Test Case : %(test_id)s. \n \n The error is caused when
    284              sending the following parameters to the API method in question : \n %(description)s''' % \
    285              {'description': description, 'test_id': test_id}
    286
    287          req_fields['project'] = {'key': project}
    288          req_fields['summary'] = summary
    289          req_fields['description'] = desc
    290          req_fields['issuetype'] = {'name': 'Bug'}
    291          req_fields['customfield_10411'] = {'value': severity}
    292          req_fields['components'] = [{'name': component}]
    293          req_fields['versions'] = [{'name': version}]
    294
    295          # if any of the required fields are not filled erase the dict
    296          if None in req_fields.values():
    297              req_fields = {}
    298
    299          return req_fields
    300
    301 -    def get_project_key(self, proj_name=''):
    302          """
    303          @param proj_name: (str) Name of Project in Jira
    304          @return: (str) Project acronym / key from Jira
    305          """
    306
    307          #get all projects from Jira
    308          projs = self.client.projects()
    309          key = None
    310
    311          #find the project in Jira who's name matches the input
    312          #and return the project key
    313          for proj in projs:
    314              if proj.name == proj_name:
    315                  key = proj.key
    316                  break
    317
    318          return key
    319