def get_sprint_list(api, proj_name2key_map, authenticate): """Retrieves the projects sprints Sprint data "may" be available through another REST API. We use this API to retrieve information about the project's sprints Args: api - string containing the URL component for the desired API proj_name2key_map - maps from poject names to project keys authenticate - tuple containing username & password to log into JIRA Returns: List containing sprint dictionaries """ sprint_list = [] view_id_rest = get_rest(api+"rapidviews/list", authenticate) for v in view_id_rest['views']: v_id = v['id'] url = api + "sprintquery/{0}?includeHistoricSprints=true".format(v_id) sprint_id_rest = get_rest(url, authenticate) proj = get_proj_from_view(v, proj_name2key_map) for s in sprint_id_rest['sprints']: sprint_list.append((v_id, s['id'], proj)) return sprint_list
def proc_sprints(api, proj_name2key_map, authenticate, counters): """Retrieves and processes the sprint information Retrieves the list of sprints associated with the current project, then scans the list to extract sprint endTime dates. These represent the sprint_closed measurement Args: api - string containing the URL component for the desired API proj_name2key_map - maps from project names to project keys authenticate - tuple containing username & password to log into JIRA counters - object containing occurrence counters Returns: No return value """ sprint_list = get_sprint_list(api, proj_name2key_map, authenticate) url_str = ("rapid/charts/scopechangeburndownchart.json?" "rapidViewId={0}&sprintId={1}") for sp in sprint_list: url = api + url_str.format(sp[0], sp[1]) sprint_rest = get_rest(url, authenticate) log_sprint_closed(sprint_rest['endTime'] ,sp[2] ,counters.sprints ,counters.measurements)
def sonar_main(config): """Main function to retrieve and process results of the SonarQube analysis Args: config - Fully Populated GripConfig object Returns: No returned value """ global GLOBALS # Hardcoded to use default local SonarQube installation # You will likely need to change this for your specific situation authenticate = ("admin", "admin") url_str = ("http://localhost:9000/api/resources?scopes=PRJ&resource={0}" "&metrics=lines,ncloc,duplicated_lines,complexity," "class_complexity,function_complexity,file_complexity,tests" "&format=json") url = url_str.format(config.sonarqube_project) print("Making Request for: '{0}'".format(url)) sonar_data = get_rest(url, authenticate) if sonar_data is not None: print("Got back {} results...".format(len(sonar_data[0]['msr']))) # Define the look-up table that will map keys from the SonarQube # results to functions that will generate the corresponging # GripMeasurements lut = {"class_complexity":class_complexity ,"complexity":complexity ,"duplicated_lines":duplicate_lines ,"file_complexity":file_complexity ,"function_complexity":function_complexity ,"lines":lines_comments ,"ncloc":loc } GLOBALS['TIMESTAMP'] = gen_timestamp(sonar_data[0]['date']) measurements = [] for v in sonar_data[0]['msr']: lut[v['key']](v, measurements) if measurements: for i in measurements: print(i) gen_json(measurements, config.json_basename + "-sq") else: err_str = "{0}Unable to retrieve issues for {1}.\n" sys.stderr.write(err_str.format(ERR_LABEL, ACCOUNT_NAME))
def handle_name_field(issue, name_field, auth, contributors): # Adapts the name/email address data to a modern # format. Also adds the name/email addr tuple to the # collection of contributors # # name_field - specifies which field we'll be transfering # # Need to make an API call to get the details of the name tmp_n = issue['fields'][name_field] url = tmp_n['value']['self'] name_info = get_rest(url, auth) name = name_info['displayName'] email = name_info['emailAddress'] # adapt the specified issue tmp_n['displayName'] = name tmp_n['emailAddress'] = email # update the contributors collection contributors[email] = name
def descr_main(config): """Main function for retrieving and displaying Java issue information Uses REST APIs to GET a project's names for various issue types, states and priorities. Formats the output for easier reading. Args: config - configuration object Returns: No return value """ labels = ["issuetype" ,"status" ,"resolution" ,"priority" ] api = config.jira_rest_api auth = (config.username, config.password) for l in labels: print("\n\n{0}:".format(l)) url = "{0}{1}{2}".format(config.server, api, l) jsn = get_rest(url, auth) dump_data(jsn)
def jira_main(config): """Main function for processing JIRA information Uses REST APIs to GET a project's issues and sprint information. Analyzes both to gather measurements that are interesting to Grip then produces a file containing a JSON representation of the extracted measurements Args: config - configuration object Returns: No return value """ # set up the counters counters = Counters() # prepare for getting issues authenticate = (config.username, config.password) server = config.server api = config.jira_rest_api # # Get the list of projects. We'll process issues for each project proj_url = "{0}{1}project".format(server, api) projects = get_rest(proj_url, authenticate) print("{0}Found {1} projects.".format(NOTE_LABEL, len(projects))) proj_name2key_map = build_proj_name2key_map(projects) # flag will be set to True if we find any issues in the projects found_issues = False for proj in projects: if proj['key'] in config.projects_to_analyze: query_str = ("search?jql=project={0}+order+by+created+asc" "&startAt=0&expand=changelog&maxResults=-1") query = query_str.format(proj['key']) url = "{0}{1}{2}".format(server, api, query) print("\nProcessing project: {0}".format(proj['key'])) print("Making Request for: {0}".format(url)) issues_rest = get_rest(url, authenticate) if issues_rest is not None: found_issues = True fstr = "{0}Retrieved {1} issues..." print(fstr.format(NOTE_LABEL, len(issues_rest['issues']))) for i in issues_rest['issues']: # There should be a more elegant way of doing this, but the # REST API apparently doesn't have a way to query for its # own version if api == "rest/api/2.0.alpha1/": # obsolete version of the API, we'll need to adapt the # issue for processing by these functions i = adapt_2alpha1_issue(i ,config ,authenticate ,counters ) # Current version of the API if i is not None: if MEASUREMENTS_OUT: proc_issue(i, config, counters) else: dump_issue(i, config, counters) else: err_str = "{0}Bad Issue Reference\n" sys.stderr.write(err_str.format(ERR_LABEL)) if found_issues: # Take care of the sprints sprint_api = config.sprint_api # "rest/greenhopper/1.0/" # If sprint_api isn't set in the configuration file, we'll skip # this step if sprint_api is not None: proc_sprints(server+sprint_api ,proj_name2key_map ,authenticate ,counters ) # and the contributors handle_contributors(counters.contributors) # if we have measurements to pass along if counters.measurements: # Measurements were placed on the list in order of issue # processing. Re-order by sorting on timestamp counters.measurements.sort(key=itemgetter(3)) if GLOBALS['VERBOSE']: for i in counters.measurements: print(i) gen_json(counters.measurements, config.json_basename + "-jira") # Dump the summary results fstr = ("\n\nCounter Class Summary:\n" " ISSUES OPEN = {0}\n" " ISSUES CLOSED = {1}\n" " ISSUES TOTAL = {2}\n\n" " REQUIREMENTS OPEN: {3}\n" " REQUIREMENTS CLOSED: {4}\n" " REQUIREMENTS TOTAL: {5}\n\n" " DEFECTS CREATED = {6}\n" " DEFECTS CLOSED = {7}\n" " DEFECTS TOTAL = {8}\n\n" " SPRINTS TOTAL = {9}\n\n" ) print(fstr.format(counters.issues.open ,counters.issues.closed ,counters.issues.total ,counters.requirements.open ,counters.requirements.closed ,counters.requirements.total ,counters.defects.created ,counters.defects.closed ,counters.defects.total ,counters.sprints.total)) else: err_str = "ERROR: Unable to retrieve issues for {0}.\n" sys.stderr.write(err_str.format(ACCOUNT_NAME))
def adapt_2alpha1_issue(issue_ref, config, auth, counters): """Adapts an issue from the old version of the API into a structure that can be processed by code expecting to work on current data Transforms the structure of the issue to conform to that of an issue from the current version of the API. All differences in data of interest will be handled in this function. Due to information missing from the older version, we're forced to make some assumptions to fill in the gaps Args: issue_ref - dictionary representing the issue to be checked config - configuration object auth - tuple with username / password for basic authentication counters - object containing occurrence counters Returns: An issue compatible with the current vesion of the API """ def progress_counter(): # Since this function needs to make several REST requests for each # issue, it can be quite slow. This progress count periodically # indicates the progress on stdout, so the user knows that we're # still going try: adapt_2alpha1_issue.iter_counter += 1 except AttributeError: reset_progress_counter() if (adapt_2alpha1_issue.iter_counter % 10) == 0: f_str = "{0}Processing {1}th issue" print(f_str.format(NOTE_LABEL, adapt_2alpha1_issue.iter_counter)) def reset_progress_counter(): # Resets the iter_counter variable to 1, can also be used to initialize # the counter adapt_2alpha1_issue.iter_counter = 1 def handle_issuetype(issue): # Adapts the issue type to the modern format tmp_i = issue['fields']['issuetype'] tmp_i['name'] = tmp_i['value']['name'] def handle_project(issue): # Adapts the project data to the modern format tmp_p = issue['fields']['project'] tmp_p['key'] = tmp_p['value']['key'] def handle_name_field(issue, name_field, auth, contributors): # Adapts the name/email address data to a modern # format. Also adds the name/email addr tuple to the # collection of contributors # # name_field - specifies which field we'll be transfering # # Need to make an API call to get the details of the name tmp_n = issue['fields'][name_field] url = tmp_n['value']['self'] name_info = get_rest(url, auth) name = name_info['displayName'] email = name_info['emailAddress'] # adapt the specified issue tmp_n['displayName'] = name tmp_n['emailAddress'] = email # update the contributors collection contributors[email] = name def handle_description(issue): # Some customer issues don't seem to have any text in their # description fields, so I'm not sure what the description is # supposed to look like in an alpha API issue. I'm kind of # guessing here. # # Cache the current info in a new key, since we'll be replacing # the value of the description key descr = issue['fields']['description'] issue['fields']['description-alpha'] = descr # use the "['fields']['description']['value'] field to populate # the new format description issue['fields']['description'] = descr.get("value") def handle_status(issue): # Adapt the status field to the current format # # Cache the current info in a new key, since we'll be replacing # the value of the status key descr = issue['fields']['status'] issue['fields']['status-alpha'] = descr # use the "['fields']['status']['value'] field to populate # the new format status issue['fields']['status'] = descr.get("value")['name'] def handle_datetime(issue, field): # Adapts a datetime field to the new format, preserves the old # field values in a new field named <field_name>-alpha. # # field - the name of the datetime field to handle. # tmp_datetime_field = issue['fields'].get(field, None) if tmp_datetime_field is not None: tmp_datetime = issue['fields'][field]['value'] stash_field = field + "-alpha" issue['fields'][stash_field] = issue['fields'][field] # use the "['fields'][field]['value'] field to populate # the new format description issue['fields'][field] = tmp_datetime elif GLOBALS['VERBOSE']: fstr = "{0}Issue '{1}' does not have a/an '{2}' field" print(fstr.format(NOTE_LABEL, issue['key'], field)) def handle_all_datetimes(issue): # Adapts all datetime fields of interest to the new format for dtm in ["created", "updated", "resolutiondate"]: handle_datetime(issue, dtm) def make_creator_field(issue): # transfers data from the reporter field to the issue's creator field # MUST be run after the reporter field has been populated correctly reporter = get_name(issue['fields']['reporter']) issue['fields']['creator'] = {"displayName":reporter[0] ,"emailAddress":reporter[1] } def make_history(issue): # Although issues obtained through the alpha REST API don't have # changelogs & histories, we need dummy values for later processing issue['changelog'] = {"histories":[]} alpha_issue = get_rest(issue_ref['self'], auth) if alpha_issue is not None: progress_counter() handle_issuetype(alpha_issue) handle_project(alpha_issue) handle_name_field(alpha_issue, "reporter", auth, counters.contributors) make_creator_field(alpha_issue) handle_all_datetimes(alpha_issue) # Since we won't have a history to process for the alpha issues: handle_name_field(alpha_issue, "assignee", auth, counters.contributors) handle_description(alpha_issue) handle_status(alpha_issue) make_history(alpha_issue) return alpha_issue