def set_jira_fields(self, auth_user, auth_token, jira_url): jira = JIRA(jira_url, basic_auth=(auth_user, auth_token)) jira_fields = {} for field in jira.fields(): jira_fields[field["name"]] = field["id"] jira_fields[field["id"]] = field["id"] return jira_fields
def _get_system_custom_fields(jira: JIRA) -> List: system_custom_fields = [] jira_fields = jira.fields() for system_field in jira_fields: if system_field['custom']: system_custom_fields.append(system_field) return system_custom_fields
class JiraWrapper(): _field_lookup = None def __init__(self, force_login=False, url=_DEFAULT_URL): auth = get_auth('JIRA', force=force_login) self.session = JIRA(url, basic_auth=auth) save_auth('JIRA', auth) def _init_field_lookup(self, force=False): if self._field_lookup is None or force: self._field_lookup = {} for field in self.session.fields(): self._field_lookup[field['name']] = field['id'] def field_id(self, name): if self._field_lookup is None: self._init_field_lookup() return self._field_lookup[name] def get_issue_field(self, issue, field): try: return getattr( self.session.issue(issue).fields(), self.field_id(field)) except JIRAError as err: logging.warning('%s', err) return None def vendors_involved(self, issue): vendors = self.get_issue_field(issue, 'Vendors involved') if vendors is None: return [] return list(map(lambda x: x.value, vendors))
def tms_login(): tms = JIRA(server='https://tms.netcracker.com', basic_auth =('rudu0916', get_pwd())) fields = tms.fields() setattr(tms, 'sprintfield', [x['id'] for x in fields if x['name'] == 'Sprint'][0]) setattr(tms, 'epiclinkfield', [x['id'] for x in fields if x['name'] == 'Epic Link'][0]) return tms
class JiraDataExtractor(): ''' Extracts structural data from Jira for further analysis ''' options = None jira = None cfg = None def __init__(self): # By default, the client will connect to a JIRA instance started from the Atlassian Plugin SDK. # See https://developer.atlassian.com/display/DOCS/Installing+the+Atlassian+Plugin+SDK # for details. self.cfg = PropertiesHandler() self.options = {'server': self.cfg.server_url} self.jira = JIRA( self.options, basic_auth=(self.cfg.user, self.cfg.pwd)) # a username/password tuple def connect(self): '''It connects against the pre configured Jira server.''' self.options = {'server': self.cfg.server_url} self.jira = JIRA( self.options, basic_auth=(self.cfg.user, self.cfg.pwd)) # a username/password tuple def getSendToCSVFile(self, fileStr): '''Sends the String to a file''' f = open( ".\\xls-export\\" + time.strftime("%Y%m%d") + "-" + time.strftime("%H%M%S") + "-jira-export.csv", "wb") f.write(fileStr) f.close() def getAllCustomFields(self, name): '''Getting all the current custom fields ID's and dump it to a CSV file for revision.''' # Fetch all fields allfields = self.jira.fields() # Make a map from field name -> field id nameMap = {field['name']: field['id'] for field in allfields} stringBuffer = StringIO() stringBuffer.write("Field Name;Code\n") for field in allfields: stringBuffer.write(field['name'] + ";" + field['id'] + "\n") self.getSendToCSVFile(stringBuffer.getvalue()) if (name != None): try: result = nameMap[name] except: return None return result else: return None
def psup_login(): psup = JIRA(server='https://psup.netcracker.com', basic_auth =('rudu0916', get_pwd()), max_retries=0) fields = psup.fields() setattr(psup, 'sprintfield', [x['id'] for x in fields if x['name'] == 'Sprint'][0]) setattr(psup, 'epiclinkfield', [x['id'] for x in fields if x['name'] == 'Epic Link'][0]) setattr(psup, 'featurelinkfield', [x['id'] for x in fields if x['name'] == 'Feature Link'][0]) return psup
class JiraBaseBackend(ServiceBackend): def __init__(self, settings, core_project=None, reporter_field=None, default_issue_type='Task'): self.settings = settings self.core_project = core_project self.reporter_field = reporter_field self.default_issue_type = default_issue_type self.jira = JIRA( server=settings.backend_url, options={'verify': False}, basic_auth=(settings.username, settings.password), validate=False) if self.reporter_field: try: self.reporter_field_id = next( f['id'] for f in self.jira.fields() if self.reporter_field in f['clauseNames']) except StopIteration: raise JiraBackendError("Can't custom field %s" % self.reporter_field)
class JiraRetriever: def __init__(self, server): self.jira = JIRA(server=server) self._users = set() def retrieve_issues(self, date_from, date_to, projects=None): str_from = date_from.strftime("%Y-%m-%d") str_to = date_to.strftime("%Y-%m-%d") def _internal(start_at): logger.info(f"Retrieving more issues starting at {start_at}") jql = f"(status != Closed OR (updated >= {str_from} AND updated <= {str_to}))" if projects: str_prj = ",".join(projects or []) jql += f" AND project in ({str_prj})" result = self.jira.search_issues(jql, startAt=start_at) self._extract_users_from_issues(result) return result return IssueIterator(_internal, self.jira.issue) def _extract_users_from_issues(self, issues): for issue in issues: fields = issue.raw["fields"] self._users.add((fields.get("assignee") or {}).get("key", None)) self._users.add((fields.get("creator") or {}).get("key", None)) def users(self): for user in self._users: if user is None: continue logger.info(f"Processing user {user}") yield self.jira.user(user, expand=["groups", "applicationRoles"]) def fields(self): for f in self.jira.fields(): logger.info(f"Processing custom field {f.get('key')}") yield f
def onProcess(ctxt, dr): # First Bit here is pilot required, probably# context = pilotpython.Context(ctxt) data = pilotpython.DataRecord(dr) props = data.getProperties() # instatiate jira jira = JIRA(server='server', basic_auth=('$(username)', '$(password)')) issues = jira.fields() jiracols = {} for i in issues: # print(i['name']+"_"+i['id']) jiracols[i['name']] = i['id'] # This bit pushes out the data on a single record as nodes - which seems the easiest option for getting out data from a single pull. Use detach nodes and process in pilot. addRecord = context.makeNewNode() addRecord.setName('record') for k, v in jiracols.items(): # props.defineStringArrayProperty(k,v) addRecord.getProperties().defineStringProperty(k, v) root = data.getRoot() root.appendChild(addRecord) return pilotpython.READY_FOR_INPUT_OR_NEW_DATA
def get_schema(cls, jira: JIRA) -> List[SchemaRow]: field_definitions: List[SchemaRow] = super().get_schema(jira) functions = get_installed_functions() for column in jira.fields(): try: type = str( cls.get_field_data(DotMap(column), "schema.type", functions)) except NameNotDefined: type = "" field_definitions.append( SchemaRow.parse_obj({ "id": str(cls.get_field_data(DotMap(column), "id", functions)), "type": type, "description": str(cls.get_field_data(DotMap(column), "name", functions)), "raw": DotMap(column), })) return field_definitions
def update_jira(release_note, cl, issues, no_issue): user = getpass.getuser() options = {'server': 'https://jira.company.com'} jira = JIRA(options, basic_auth=('myuser', 'mypass')) cl_field = "" release_note_field = "" for field in jira.fields(): if field['name'] == 'Changeset': cl_field = field['id'] elif field['name'] == 'Release Note': release_note_field = field['id'] if cl_field != "" and release_note_field != "": break jira_issues = [] jira_not_found = [] for issue in issues: try: jira_issue = jira.issue(issue) jira_issues.append(jira_issue) except Exception as e: print(e) jira_not_found.append(issue) for jira_issue in jira_issues: orig_status = str(jira_issue.fields.status) # Skip JIRAs that are in one of the resolved states if (orig_status != "Fixed" and orig_status != "Accepted" and orig_status != "Closed" and orig_status != "Void" and orig_status != RESOLVED_STATUS): is_jira_ccr = (str(jira_issue.fields.issuetype) == ISSUE_TYPE_CCR) if is_jira_ccr: regression_test_name = jira_issue.raw['fields'][ regression_test_field] if (orig_status == REPORTER_REVIEW_STATUS): transition_name = FIX_ACCEPTED_TRANSITION else: transition_name = FIXED_STATUS_TRANSITION else: # This is a JIRA story if (orig_status == REPORTER_REVIEW_STATUS): transition_name = FIX_ACCEPTED_TRANSITION else: transition_name = ACCEPTED_STATUS try: jira.transition_issue(jira_issue, transition_name) # In case this is a JIRA CCR in State Reporter Review, 2 state transitions are needed to get to Fixed, so adding the second one # A false error is expected if the "Previous State" was "Fixed" but this is not a main flow scenario if (is_jira_ccr and (orig_status == REPORTER_REVIEW_STATUS)): transition_name = FIXED_STATUS_TRANSITION jira.transition_issue(jira_issue, transition_name) except Exception as error: if is_jira_ccr: print( "Cannot transit JIRA CCR %s to status 'Fixed' from '%s' using the '%s' transition" % (str(jira_issue), jira_issue.fields.status, transition_name)) else: print( "Cannot transit JIRA Story %s to status 'Accepted' from '%s' using the '%s' transition" % (str(jira_issue), jira_issue.fields.status, transition_name)) print("Error: %s" % error) jira.add_comment(jira_issue, comment) release_note = release_note + "\n" + str( jira_issue.raw['fields'][release_note_field]) jira_issue.update(fields={ cl_field: cl, release_note_field: release_note }) print("\n\nINFO: ") else: print("No transaction is required for issue: " + str(jira_issue) + " JIRA status is: " + str(jira_issue.fields.status)) return ReturnValues.SUCCESS
class JiraClient(object): """ Helper class for the JIRA """ def __init__(self, url, username, password): """ :param url: :param username: :param password: :return: """ self.url = url self.webhook_url = self.url.strip('/') + '/rest/webhooks/1.0/webhook' self.basic_auth = (username, password) self.client = JIRA(url, basic_auth=self.basic_auth) def __str__(self): return '{} {}'.format(self.__class__.__name__, self.url) ################ # Accounts # ################ def groups(self): """ :return: """ return self.client.groups() def users(self, json_result=True): """ :param json_result: :return: """ return self._search_users('_', json_result=json_result) def users_by_email(self, email, json_result=True): """ :param email: :param json_result: :return: """ return self._search_users(email, json_result=json_result) def users_by_group(self, name): """ :param name: :return: """ try: return self.client.group_members(name) except JIRAError as exc: logger.warning(exc) return dict() def _search_users(self, qstr, json_result=True): """ :param qstr: :param json_result: :return: """ def _user_dict_format(user): # Note: Keep the consistent return format with the users_by_group method return {'key': user.key, 'active': user.active, 'fullname': user.displayName, 'email': user.emailAddress } users = self.client.search_users(qstr) if json_result: # Note: Keep the consistent return format with the users_by_group method return [_user_dict_format(user) for user in users] else: return users ############# # Project # ############# def create_project(self, key): """ :param key: :return: """ return self.client.create_project(key) def get_project(self, key, json_result=True): """ :param key: :param json_result: :return: """ if json_result: return self.client.project(key).raw else: return self.client.project(key) def get_projects(self, json_result=True): """ :param json_result: :return: """ project_objs = self.client.projects() if json_result: return [_each.raw for _each in project_objs] else: return project_objs def delete_project(self, key): """ :param key: :return: """ return self.client.delete_project(key) ############# # Version # ############# def get_project_versions(self, name, json_result=True): """ :param name: project name :param json_result: :return: """ try: version_objs = self.client.project_versions(name) if json_result: return [_each.name for _each in version_objs] else: return version_objs except Exception as exc: logger.warn(exc) return [] def create_project_version(self, name, project_name, **kwargs): """ :param name: version name :param project_name: project name :param kwargs: :return: """ return self.client.create_version(name, project_name, **kwargs) ############# # fields # ########### # def get_fields(self): """ :return: """ return self.client.fields() def get_non_custom_fields(self): """ :return: """ return [each for each in self.client.fields() if not each.get('custom', True)] def get_custom_fields(self): """ :return: """ return [each for each in self.client.fields() if each.get('custom', True)] def get_field_id_by_name(self, name): """ :param name: :return: """ ids = [each['id'] for each in self.client.fields() if each.get('name', '') == name] if ids: return ids[0] else: return None def get_field_id_for_hours_left(self): """ :return: """ # For Argo customized field name = 'Hrs Left' return self.get_field_id_by_name(name) ############ # issues # ############ def get_issue(self, name, json_result=True): """ :param name: :param json_result: :return: """ try: issue_obj = self.client.issue(name) except JIRAError as exc: logger.warn('Not found: %s', exc) return None if json_result: issue_dict = copy.deepcopy(issue_obj.raw['fields']) issue_dict['url'] = issue_obj.self issue_dict['id'] = issue_obj.id issue_dict['key'] = issue_obj.key # Replace custom field name return issue_dict else: return issue_obj def add_fix_version_to_issue(self, issue_name, version_name, issuetype=None): """ :param issue_name: :param version_name: :param issuetype: :return: """ return self._add_versions_from_issue(issue_name, version_name, issuetype=issuetype, _version_type='fixVersions') def add_affected_version_to_issue(self, issue_name, version_name, issuetype=None): """ :param issue_name: :param version_name: :param issuetype: :return: """ return self._add_versions_from_issue(issue_name, version_name, issuetype=issuetype, _version_type='versions') def _add_versions_from_issue(self, issue_name, version_name, issuetype=None, _version_type='fixVersions'): """ :param issue_name: :param version_name: :param issuetype: :param _version_type: :return: """ assert _version_type in ['fixVersions', 'versions'], 'Unsupported version type' issue_obj = self.get_issue(issue_name, json_result=False) if not issue_obj: return None if issuetype is not None and issue_obj.fields.issuetype.name.lower() != issuetype.lower(): logger.info('SKIP. The issue type is %s, expected issue type is %s', issue_obj.fields.issuetype.name.lower(), issuetype.lower()) return None logger.info('Update issue %s, with %s %s', issue_obj.key, _version_type, version_name) ret = issue_obj.add_field_value(_version_type, {'name': version_name}) issue_obj.update() return ret def remove_affected_versions_from_issue(self, issue_name, versions_to_remove): """ :param issue_name: :param versions_to_remove: :return: """ return self._remove_versions_from_issue(issue_name, versions_to_remove, _version_type='versions') def remove_fix_versions_from_issue(self, issue_name, versions_to_remove): """ :param issue_name: :param versions_to_remove: :return: """ return self._remove_versions_from_issue(issue_name, versions_to_remove, _version_type='fixVersions') def _remove_versions_from_issue(self, issue_name, versions_to_remove, _version_type='fixVersions'): """ :param issue_name: :param versions_to_remove: :param _version_type: :return: """ assert _version_type in ['fixVersions', 'versions'], 'Unsupported version type' if type(versions_to_remove) not in [list, tuple, set]: versions_to_remove = [versions_to_remove] versions = [] issue_obj = self.get_issue(issue_name, json_result=False) for ver in getattr(issue_obj.fields, _version_type): if ver.name not in versions_to_remove: versions.append({'name': ver.name}) issue_obj.update(fields={_version_type: versions}) def create_issue(self, project, summary, description=None, issuetype='Bug', reporter=None, **kwargs): """ :param project: :param summary: :param description: :param issuetype: :param reporter: :param kwargs: :return: """ # { # "fields": { # "project": # { # "key": "TEST" # }, # "summary": "Always do right. This will gratify some people and astonish the REST.", # "description": "Creating an issue while setting custom field values", # "issuetype": { # "name": "Bug" # }, # "customfield_11050" : {"Value that we're putting into a Free Text Field."} # } # } fields_dict = dict() for k, v in kwargs.items(): fields_dict[k] = v fields_dict['project'] = {'key': project} fields_dict['description'] = description or '' fields_dict['summary'] = summary fields_dict['issuetype'] = {'name': issuetype} if reporter: users = self.users_by_email(reporter, json_result=False) if users: fields_dict['reporter'] = {'name': users[0].name} return self.client.create_issue(fields=fields_dict) def update_issue(self, name, **kwargs): """ :param name: :param kwargs: :return: """ issue_obj = self.get_issue(name, json_result=False) issue_obj.update(**kwargs) ###################### # issue comments # ###################### def get_issue_comments(self, issue_name, latest_num=5, json_result=True): """ :param issue_name: :param latest_num: :param json_result: :return: """ try: comments = self.client.comments(issue_name) except JIRAError as exc: logger.warn(exc) return [] comments = comments[::-1][:latest_num] if json_result: return [each.raw for each in comments] else: return comments def add_issue_comment(self, issue_name, msg, commenter=None): """ :param issue_name: :param msg: :param commenter: :return: """ if commenter: users = self.users_by_email(commenter, json_result=False) if users: msg_header = 'The comment is created by {}({}) from AX system. \n\n'.\ format(users[0].displayName, users[0].emailAddress) msg = msg_header + msg return self.client.add_comment(issue_name, msg) ############### # issue type # ############### def get_issue_types(self, json_result=True): """ :param json_result: :return: """ objs = self.client.issue_types() if json_result: return [obj.raw for obj in objs] else: return objs def get_issue_type_by_name(self, name, json_result=True): """ :param name: :param json_result: :return: """ try: obj = self.client.issue_type_by_name(name) except KeyError as exc: logger.warn(exc) return None else: if json_result: return obj.raw else: return obj ############# # Query # ############# def query_issues(self, **kwargs): """ :param kwargs: :return: max_results: maximum number of issues to return. Total number of results If max_results evaluates as False, it will try to get all issues in batches. json_result: JSON response will be returned when this parameter is set to True. Otherwise, ResultList will be returned """ SUPPORTED_KEYS = ('project', 'status', 'component', 'labels', 'issuetype', 'priority', 'creator', 'assignee', 'reporter', 'fixversion', 'affectedversion') max_results = kwargs.pop('max_results', 100) _json_result = kwargs.pop('json_result', False) jql_str_list = [] for k, v in kwargs.items(): if k not in SUPPORTED_KEYS: continue jql_str_list.append('{} = "{}"'.format(k.strip(), v.strip())) if jql_str_list: jql_str = ' AND '.join(jql_str_list) else: jql_str = '' # Fetch ALL issues try: ret = self.client.search_issues(jql_str, maxResults=max_results, json_result=_json_result) except Exception as exc: logger.warn(exc) ret = {"issues": []} return ret ################ # Query Issues # ################ def get_issues_by_project(self, project_name, **kwargs): """ :param project_name: :param kwargs: :return: """ return self.query_issues(project=project_name, **kwargs) def get_issues_by_component(self, component, **kwargs): """ :param component: :param kwargs: :return: """ return self.query_issues(component=component, **kwargs) def get_issues_by_assignee(self, assignee, **kwargs): """ :param assignee: :param kwargs: :return: """ return self.query_issues(assignee=assignee, **kwargs) def get_issues_by_status(self, status, **kwargs): """ :param status: :param kwargs: :return: """ return self.query_issues(status=status, **kwargs) def get_issues_by_label(self, labels, **kwargs): """ :param labels: :param kwargs: :return: """ return self.query_issues(labels=labels, **kwargs) def get_issues_by_fixversion(self, fix_version, **kwargs): """ :param fix_version: :param kwargs: :return: """ return self.query_issues(fixverion=fix_version, **kwargs) def get_issues_by_affectedversion(self, affected_version, **kwargs): """ :param affected_version: :param kwargs: :return: """ return self.query_issues(affectedversion=affected_version, **kwargs) ################ # Query Hours # ################ def get_total_hours(self, **kwargs): """ :param kwargs: :return: """ all_issues = self.query_issues(**kwargs) field_id = self.get_field_id_for_hours_left() hours = [getattr(iss_obj.fields, field_id) for iss_obj in all_issues] return sum([float(each) for each in hours if each]) def get_total_hours_by_project(self, project_name): """ :param project_name: :return: """ return self.get_total_hours(project=project_name) def get_total_hours_by_component(self, component): """ :param component: :return: """ return self.get_total_hours(component=component) def get_total_hours_by_assignee(self, assignee): """ :param assignee: :return: """ return self.get_total_hours(assignee=assignee) def get_total_hours_by_label(self, label): """ :param label: :return: """ return self.get_total_hours(labels=label) ############## # webhook # ############## def create_ax_webhook(self, url, projects=None): """Create AX Jira webhook :param url: :param projects: :return: """ payload = copy.deepcopy(AX_JIRA_WEBHOOK_PAYLOAD) payload['name'] = payload['name'] + self._get_cluster_name() payload['url'] = url filter_dict = self._generate_project_filter(projects) logger.info('Webhook project filter is: %s', filter_dict) payload.update(filter_dict) return self._requests(self.webhook_url, 'post', data=payload) def get_ax_webhook(self): """Get AX Jira webhook :return: """ response = self._requests(self.webhook_url, 'get') wh_name = AX_JIRA_WEBHOOK_PAYLOAD['name'] + self._get_cluster_name() ax_whs = [wh for wh in response.json() if wh['name'] == wh_name] if not ax_whs: logger.error('Could not get Jira webhook for this cluster: %s, ignore it', wh_name) else: return ax_whs[0] def update_ax_webhook(self, projects=None): """Update AX Jira webhook :param projects: :return: """ ax_wh = self.get_ax_webhook() if ax_wh: filter_dict = self._generate_project_filter(projects) logger.info('Webhook project filter is: %s', filter_dict) logger.info('Update the webhook %s', ax_wh['self']) return self._requests(ax_wh['self'], 'put', data=filter_dict) else: logger.warn('Skip the webhook update') def delete_ax_webhook(self): """Delete AX Jira webhook :return: """ response = self._requests(self.webhook_url, 'get') wh_name = AX_JIRA_WEBHOOK_PAYLOAD['name'] + self._get_cluster_name() ax_whs = [wh for wh in response.json() if wh['name'] == wh_name] for wh in ax_whs: logger.info('Delete webhook %s', wh['self']) self._delete_webhook(url=wh['self']) def get_ax_webhooks(self): """Get all AX webhooks :return: """ response = self._requests(self.webhook_url, 'get') webhooks = response.json() # filter out non-ax webhooks return [wh for wh in webhooks if wh['name'].startswith(AX_JIRA_WEBHOOK_PAYLOAD['name'])] def delete_ax_webhooks(self): """Delete all AX Jira webhooks :return: """ ax_whs = self.get_ax_webhooks() for wh in ax_whs: logger.info('Delete webhook %s', wh['self']) self._delete_webhook(url=wh['self']) def _generate_project_filter(self, projects): """ :param projects: :return: """ if not projects: filter_str = '' else: project_filter_list = [] project_objs = self.get_projects(json_result=False) for pkey in projects: ps = [p for p in project_objs if p.key == pkey] if not ps: logger.error('Could not get project %s, ignore it', pkey) else: project_filter_list.append('Project = {}'.format(ps[0].name)) filter_str = ' AND '.join(project_filter_list) return {'filters': {'issue-related-events-section': filter_str } } def _delete_webhook(self, url=None, id=None): """Delete webhook :param url: :param id: :return: """ if url is None: url = self.webhook_url + '/' + str(id) return self._requests(url, 'delete') def _get_cluster_name(self): """ :return: """ return os.environ.get('AX_CLUSTER', UNKNOWN_CLUSTER) def _requests(self, url, method, data=None, headers=None, auth=None, raise_exception=True, timeout=30): """ :param url: :param method: :param data: :param headers: :param auth: :param raise_exception: :param timeout: :return: """ headers = {'Content-Type': 'application/json'} if headers is None else headers auth = self.basic_auth if auth is None else auth try: response = requests.request(method, url, data=json.dumps(data), headers=headers, auth=auth, timeout=timeout) except requests.exceptions.RequestException as exc: logger.error('Unexpected exception occurred during request: %s', exc) raise logger.debug('Response status: %s (%s %s)', response.status_code, response.request.method, response.url) # Raise exception if status code indicates a failure if response.status_code >= 400: logger.error('Request failed (status: %s, reason: %s)', response.status_code, response.text) if raise_exception: response.raise_for_status() return response
class JiraIntegrator: def __init__(self, user, api_token, host): options = {"server": host} self.jira_connection = JIRA(options, basic_auth=(user, api_token)) self.event_handlers = { enums.GitHubAction.OPENED.value: self.create_new_jira_issue, enums.GitHubAction.CREATED.value: self.create_new_jira_issue, enums.GitHubAction.EDITED.value: self.update_jira_issue, enums.GitHubAction.LABELED.value: self.update_jira_label, enums.GitHubAction.UNLABELED.value: self.update_jira_label, enums.GitHubAction.CLOSED.value: self.close_jira_issue, enums.GitHubAction.REOPENED.value: self.reopen_jira_issue, } def handle(self, event): status_code = 500 try: handler = self.event_handlers[event["action"]] handler(event["issue"], event["repository"]) status_code = 200 except (KeyError, ValueError) as err: status_code = 404 logging.exception(f"Can not find: {repr(err)}") except Exception as err: logging.exception(f"Error occurs: {repr(err)}") return { "statusCode": status_code, "headers": {"Access-Control-Allow-Origin": "*"}, } def create_new_jira_issue(self, issue, repository): issuetype = {"name": utils.get_issue_type(issue["labels"])} github_link_field_name = self._get_field_id("GitHub Link") github_author_field_name = self._get_field_id("GitHub Author") github_link = utils.find_github_link(repository["full_name"], issue["number"]) labels = utils.get_jira_labels(issue["labels"]) project = utils.find_project(repository) fields = { "project": project, "summary": issue["title"], "description": issue["body"], "issuetype": issuetype, "labels": labels, github_link_field_name: github_link, github_author_field_name: issue.get("user", {}).get("login"), } component = None try: if "mirumee/saleor-dashboard" in github_link: component = self.jira_connection.component(COMPONENT_ID_DASHBOARD) if "mirumee/saleor-docs" in github_link: component = self.jira_connection.component(COMPONENT_ID_DOCS) except Exception as err: logging.exception(f"Failed to assign component: {repr(err)}") if component: fields["components"] = [{"name": component.name}] self.jira_connection.create_issue(**fields) def update_jira_issue(self, issue, repository): jira_issue = self._find_jira_issue(repository["full_name"], issue["number"]) fields = {"summary": issue["title"], "description": issue["body"]} jira_issue.update(fields=fields) def update_jira_label(self, issue, repository): jira_issue = self._find_jira_issue(repository["full_name"], issue["number"]) issuetype = {"name": utils.get_issue_type(issue["labels"])} labels = utils.get_jira_labels(issue["labels"]) fields = {"issuetype": issuetype, "labels": labels} jira_issue.update(fields=fields) def close_jira_issue(self, issue, repository): jira_issue = self._find_jira_issue(repository["full_name"], issue["number"]) ready_to_test_transition = self._get_ready_to_test_transition(jira_issue) self.jira_connection.transition_issue( jira_issue, ready_to_test_transition["id"] ) def reopen_jira_issue(self, issue, repository): jira_issue = self._find_jira_issue(repository["full_name"], issue["number"]) to_do_transition = self._get_to_do_transition(jira_issue) self.jira_connection.transition_issue(jira_issue, to_do_transition["id"]) def _get_field_id(self, field_name): fields = self.jira_connection.fields() selected_field = filter( lambda field: field["name"].lower() == field_name.lower(), fields ) return next(selected_field)["key"] def _find_jira_issue(self, github_project, issue_number): query = '"GitHub Link" = "{github_link}"'.format( github_link=utils.find_github_link(github_project, issue_number) ) issues = self.jira_connection.search_issues(query) if not issues: raise ValueError(f"Can not find jira issue with number {issue_number}") return issues[0] def _get_to_do_transition(self, issue): return self._get_transition_by_name(issue, enums.JiraTransition.TO_DO.value) def _get_ready_to_test_transition(self, issue): return self._get_transition_by_name( issue, enums.JiraTransition.READY_TO_TEST.value ) def _get_transition_by_name(self, issue, transition): transitions = self.jira_connection.transitions(issue) ready_to_test_transition = next( filter(lambda tr: tr["name"].lower() == transition, transitions) ) return ready_to_test_transition
if (not args.username): username = getpass.getuser() password = getpass.getpass('Enter your Jira password: '******'https://issues.voltdb.com/' try: jira = JIRA(jira_url, basic_auth=(username, password), options=dict(verify=False)) except: sys.exit('FATAL: Unable to log in ' + username) #Get Release Note field id relnote_field = [ f['id'] for f in jira.fields() if 'Release Note' in f['name'] ][0] def is_valid_jid(jid): #Return true of it looks like a valid ticket #return jid.split('-')[1].isdigit() and len(jid.split('-')) == 2 if not len(jid.split('-')) == 2: return False return jid.split('-')[1].isdigit() def cleanstring(str): return ' '.join(str.strip().split()) # Get the release notes from relnotesparser as a two column array
class JiraSession(object): def __init__(self, server, account, password, verify=True): """ Init Jira Session :param server: :param account: :param password: :param verify: """ self.__server = server self.__account = account self.__password = password self.__jira_opts = { 'server': self.__server, 'verify': verify, } self.__session = JIRA(self.__jira_opts, basic_auth=(self.__account, self.__password)) def __enter__(self): assert self.__session.current_user() == self.__account return self def __exit__(self, exc_type, exc_val, exc_tb): self.__session.close() def get_user(self): """ Get jira user :return: """ logging.info(u'Get JIRA Current User') return self.__session.current_user() def search_issues(self, jql): """ Search issues via JQL :param jql: :return: """ logging.info(u'JIRA Search: %s' % jql) return self.__session.search_issues(jql_str=jql, maxResults=128, json_result=True) # def get_issue_count(self, jql: str, issue_summary: dict, issue_key: str): # """ # Search issues via JQL and return count # :param jql: # :param issue_summary: # :param issue_key: # :return: # """ # logging.info(u'JIRA Issue Count: %s' % jql) # issue_summary[issue_key] = int(self.search_issues(jql).get('total')) # return True def get_projects(self): """ Get jira projects :return: <key, name, id> """ logging.info(u'Get JIRA Projects') return self.__session.projects() def get_sprints(self): """ Get jira sprints :return: <name, id> """ logging.info(u'Get JIRA Sprints') jira_sprints = list() for board in self.__session.boards(): _sprints = self.__session.sprints(board.id) jira_sprints = jira_sprints + _sprints return jira_sprints def get_issue_fields(self): """ Get jira fields :return: [{'name':'','id':''}] """ logging.info(u'Get JIRA Fields') _fields = list() for _field in self.__session.fields(): _fields.append({'name': _field['name'], 'id': _field['id']}) return _fields def get_issue_types(self): """ Get jira issue types :return: <name, id> """ logging.info(u'Get JIRA Issue Types') return self.__session.issue_types() def get_issue_statuses(self): """ Get issue statuses :return: <name, id> """ logging.info(u'Get JIRA Issue Statuses') return self.__session.statuses() def get_project_versions(self, pid: str): """ Get project versions :param pid: :return: [<name, id>] """ logging.info(u'Get JIRA Project %s Versions' % pid) return self.__session.project_versions(project=pid)
file.close() else: print("No file config.txt") file = config.open("w") file.write("JSESSIONID=\n") file.write("BASIC_LOGIN=\n") file.write("BASIC_PASS=\n") file.write("URL=") file.close() print("Created file config.txt. Fill it up with credentials!") exit() options = {"cookies": {"JSESSIONID": sessionid}} jira = JIRA(url, basic_auth=(basic_login, basic_pass), options=options) nameMap = {field['name']: field['id'] for field in jira.fields()} print("Getting issues...") issues = searchIssues( "status = Done AND 'Story Points' is not EMPTY AND project != 'Zakupy Fenige' AND project != Urlopy AND resolved > startOfMonth(-6) ORDER BY resolved DESC", "Story Points,project,resolutiondate,customfield_10004") print("Getting issues... DONE") data = { "Key": [], "Project": [], "Story Points": [], "Team Name": [], "Resolve Date": [] }
def main(): # Parse arguments. Assume any error handling happens in parse_args() try: args = parse_args() except Exception as err: print(f"Failed to parse arguments: {err}", file=sys.stderr) start_datetime = args["start_datetime"] end_datetime = args["end_datetime"] detailed = args["detailed"] num_issues_done = 0 num_done_issues_code_reviewed = 0 # Connect to Jira options = {"server": "https://opensciencegrid.atlassian.net"} jira = JIRA(options) # We need to find the key of the "Development" field, to determine if an issue has commits development_field_key = None fields = jira.fields() name_map = {field['name']: field['id'] for field in fields} if "Development" in name_map: development_field_key = name_map["Development"] # Iterate over all completed issues in the HTCONDOR project issues = jira.search_issues( "project = HTCONDOR AND type in (Improvement, Bug) AND status = Done", expand="changelog", maxResults=False) for issue in issues: issue_changelog = issue.changelog.histories issue_isdone = False for change in issue_changelog: for item in change.items: # Issues can be marked Done multiple times, so break after first occurrence if issue_isdone: break # Check if this change was setting the status to Done if item.field == "status" and item.toString == "Done": # Check if this issue has any code commits. If not, don't count it. development_data = getattr(issue.fields, development_field_key) if development_data == "{}": continue # Strip the time offset from the change.created string changed = change.created[0:change.created.rfind("-")] # Was this issue set to Done status between the provided end and start dates? changed_datetime = datetime.strptime( changed, "%Y-%m-%dT%H:%M:%S.%f") if changed_datetime > start_datetime and changed_datetime < end_datetime: if detailed is True: print( f"{issue.key}: {issue.fields.summary}, Marked Done: {changed_datetime.strftime('%Y-%m-%d %H:%M:%S')}" ) num_issues_done += 1 issue_isdone = True # Now check the issue comments for a "code review" text entry comments = jira.comments(issue) if len(comments) > 0: for comment in comments: if "code review" in comment.body.lower()[0:20]: num_done_issues_code_reviewed += 1 if detailed is True: print("\tThis issue was code reviewed") break print( f"\nBetween {start_datetime.strftime('%Y-%m-%d')} and {end_datetime.strftime('%Y-%m-%d')}:\n" ) print(f"{num_issues_done} HTCONDOR issues were marked Done") print( f"{num_done_issues_code_reviewed} of these completed issues were code reviewed" ) if num_issues_done > 0: print( f"Code review rate: {round(num_done_issues_code_reviewed*100/num_issues_done, 2)}%\n" ) else: print(f"No issues marked Done between the dates specified.")
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))
class JiraAnalysis: def __init__(self, jira_url, jira_username, jira_token, jira_team_labels): self.issue_cache = {} self.jira = JIRA(jira_url, basic_auth=(jira_username, jira_token)) self.jira_team_labels = jira_team_labels self.sprint_field = self.get_custom_field_key("Sprint") self.story_point_field = self.get_custom_field_key("Story Points") self.investment_area_field = self.get_custom_field_key( "Investment Area") self.epic_link_field = self.get_custom_field_key("Epic Link") if self.sprint_field is None: raise Exception("Failed to find Sprint field") if self.story_point_field is None: raise Exception("Failed to find Story Point field") if self.investment_area_field is None: raise Exception("Failed to find Investment Area field") if self.epic_link_field is None: raise Exception("Failed to find Epic Link field") # Retrieve the custom field matching to a particular name since JIRA gives custom fields a random ID def get_custom_field_key(self, name): all_fields = self.jira.fields() for field in all_fields: if field["name"] == name: return field["key"] return None # Retrieve the team for an issue - based on labels def get_team(self, issue): for label in issue.fields.labels: for team_label in self.jira_team_labels: if team_label.lower() == label.lower(): return team_label return None # Retrieve the investment area def get_investment_area(self, issue): ia = getattr(issue.fields, self.investment_area_field) if ia: return ia return [] # Retrieve the epic link def get_epic_link(self, issue): return getattr(issue.fields, self.epic_link_field, "") # Get issue type, a bit weird since it's off of fields and needs to be converted to string def get_issue_type(self, issue): return issue.fields.issuetype.name.lower() # Get description from an issue def get_description(self, issue): return issue.fields.description # Get story points from an issue def get_story_points(self, issue): return get_or_float_zero(issue.fields, self.story_point_field) # Get the priority of an issue def get_priority(self, issue): for label in issue.fields.labels: # TODO: Update this for other formats # Take the first label found to avoid double counting if "2018:q1:" in label.lower() or "2018:q2" in label.lower(): try: return int(label.split(":")[-1]) except: return 100 # The Misc priority return 100 # For a set of issues get stats by priority def get_priority_stats(self, issues): priority_count = Counter() priority_story_points = defaultdict(float) no_priority_stories = [] for issue in issues: priority = self.get_priority(issue) if priority is not None: story_points = self.get_story_points(issue) priority_count.update([priority]) priority_story_points[priority] += float(story_points) else: no_priority_stories.append(issue) return priority_count, priority_story_points, no_priority_stories # Wrap the pagination code so user doesn't have to do it themselves def get_issues(self, query): if query in self.issue_cache: return self.issue_cache[query] all_issues = [] MAX_RESULTS = 100 issues = self.jira.search_issues(query, maxResults=MAX_RESULTS) total = issues.total all_issues.extend(list(issues)) if total > MAX_RESULTS: # Actually need to paginate for page in range(1, int(ceil(1.0 * total / MAX_RESULTS))): logger.info("Getting page %s", page) issues = self.jira.search_issues(query, maxResults=MAX_RESULTS, startAt=page * MAX_RESULTS) logger.info("Retrieved %s issues", len(issues)) all_issues.extend(list(issues)) logger.info("Total retrieved %s", len(all_issues)) self.issue_cache[query] = all_issues return all_issues # Clean up and write issues to a CSV def write_issues(self, start_date, end_date, fn): issues = self.get_issues(self.get_issue_query(start_date, end_date)) with open(fn, "w") as f: w = csv.writer(f) w.writerow([ "ticket", "summary", "team", "priority", "story_points", "assignee", "created_date", "resolved_date", "type", "investment_area", "epic", ]) for issue in issues: w.writerow([ issue, issue.fields.summary.encode("utf-8"), self.get_team(issue), self.get_priority(issue), self.get_story_points(issue), issue.fields.assignee if issue.fields.assignee else "None", issue.fields.created, issue.fields.resolutiondate, self.get_issue_type(issue), ",".join(self.get_investment_area(issue)), self.get_epic_link(issue), ]) # Get all done stories and bugs between a date range def get_issue_query(self, start_date, end_date): return ('project = "TL" and status = Done and resolutiondate >= "' + start_date + '" and resolutiondate <= "' + end_date + '" AND type in ("story", "bug", "task", "spike", "access")') # Just get the list of words def get_descriptions_words(self, start_date, end_date): issues = self.get_issues(self.get_issue_query(start_date, end_date)) words = [] for issue in issues: desc = self.get_description(issue) if desc is not None: words.extend(desc.split(" ")) return words # Measure analytics per priority def analyze_priorities(self, start_date, end_date): issues = self.get_issues(self.get_issue_query(start_date, end_date)) ( priority_count, priority_story_points, no_priority_stories, ) = self.get_priority_stats(issues) logger.info("Priority counts") logger.info(print_dict(priority_count, "\n")) logger.info("Priority story points") logger.info(print_dict(priority_story_points, "\n")) logger.info("No priorities") for issue in no_priority_stories: logger.info("\t %s %s", issue, issue.fields.summary) # Measrure # of sprints to do a story def analyze_sprint_lag(self, start_date, end_date): team_sprint_counts = defaultdict(list) team_sprint_story_point_sum = defaultdict(float) team_story_point_sum = defaultdict(float) team_bugs = defaultdict(int) issues = self.get_issues(self.get_issue_query(start_date, end_date)) for issue in issues: team = self.get_team(issue) try: num_sprints = len(getattr(issue.fields, self.sprint_field)) except: num_sprints = 0 story_points = get_or_float_zero(issue.fields, self.story_point_field) issue_type = self.get_issue_type(issue) # Has a team and was actually done via sprint process if team and num_sprints > 0 and issue_type == "story": team_sprint_counts[team].append(num_sprints) if story_points > 0: team_sprint_story_point_sum[ team] += num_sprints * story_points team_story_point_sum[team] += story_points if issue_type == "bug": team_bugs[team] += 1 logger.info("Team\tSprint Lag\tSP Sprint Lag\tBugs") for team, counts in team_sprint_counts.items(): if team_sprint_story_point_sum[team] > 0: logger.info( "%s\t%s\t%s\t%s", team, sum(counts) * 1.0 / len(counts), team_sprint_story_point_sum[team] / team_story_point_sum[team], team_bugs[team], ) else: logger.info("%s\tNA\t%NA\t%s", team, team_bugs[team]) # Measure # of story points done per assignee def analyze_story_points(self, start_date, end_date): user_story_point_sum = Counter() user_data = {} issues = self.get_issues(self.get_issue_query(start_date, end_date)) for issue in issues: story_points = get_or_float_zero(issue.fields, self.story_point_field) assignee = str( issue.fields.assignee) if issue.fields.assignee else "None" user_story_point_sum.update({assignee: int(story_points)}) if assignee not in user_data: user_data[assignee] = defaultdict(int) issue_type = self.get_issue_type(issue) user_data[assignee][issue_type + "_cnt"] += 1 user_data[assignee][issue_type + "_story_points"] += int(story_points) logger.info("User\tSP\tStories\tBugs") for user, story_points in user_story_point_sum.most_common(100): logger.info("%s\t%s\t%s", user, story_points, user_data[user])
def generate(self): storage_passwords = self.service.storage_passwords jira_username = None jira_password = None for credential in storage_passwords: credential_realm = credential.content.get('realm') if credential_realm == self.jirasearch_realm: jira_username = credential.content.get('username') jira_password = credential.content.get('clear_password') if not jira_username: raise Exception("Did not find username/password for realm: {}".format(self.jirasearch_realm)) try: jira_url = self.service.confs['jirasearch']['jirasearch']['jira_url'] except: raise Exception("Did not find jira_url setting in [jirasearch] in jirasearch.conf") try: jira = JIRA(jira_url, basic_auth=(jira_username, jira_password)) except: raise Exception("Unable to connect to JIRA with configured URL/username/password") field_id_for_field = {} for field in jira.fields(): field_id_for_field[field['name']] = field['id'] # if we can't access search_et, set it to the epoch time try: search_et_epoch = self.search_results_info.search_et except: search_et_epoch = 0.0 # if we can't access search_lt, set it to now try: search_lt_epoch = self.search_results_info.search_lt except: search_lt_epoch = time.time() # format timestamps to be appropriate for JQL search_et_jira = datetime.fromtimestamp(search_et_epoch).strftime("%Y/%m/%d %H:%M") search_lt_jira = datetime.fromtimestamp(search_lt_epoch).strftime("%Y/%m/%d %H:%M") # perform substitution templated_query = Template(self.query).render(earliest=search_et_jira, latest=search_lt_jira) events = [] for issue in jira.search_issues(templated_query, maxResults=self.limit): event = { '_time': time.mktime(time.strptime(issue.fields.created, "%Y-%m-%dT%H:%M:%S.000+0000")), '_raw': "{}: {}".format(issue.key, issue.fields.summary), 'key': issue.key, } for field in field_id_for_field: field_id = field_id_for_field[field] try: field_value = getattr(issue.fields, field_id) if isinstance(field_value, list): event[field] = [] for value in field_value: event[field].append("{}".format(value)) elif field_value: event[field] = "{}".format(field_value) else: event[field] = [] except: event[field] = [] events.append(event) sorted_events = sorted(events, None, lambda x: x['_time'], True) for event in sorted_events: yield event
class JiraIssues(object): APPLICATION = {"type": "www.hackerone.comr", "name": "Hacker One"} SCOPE = ''' h4.Scope ---- asset type: %(type)s asset identifier: %(identifier)s\n''' DESCRIPTION = ''' h4.Report Info ---- Report State: %(state)s Reporter: %(reporter)s Assignee: %(assignee)s Report Created: %(created)s Report Last Activity: %(last_activity)s h4.Weakness ---- name: %(name)s description: %(w_description)s id: %(id)s h4.Severity ---- rating: %(rating)s score: %(score)s' h4.Description ---- %(description)s ''' def __init__(self, server, username, password, project): """Inits jira client. This current setup requires a jira username to be setup with the appropriate permissions in the jira project :type server: string :param server: jira url :type username: string :param username: token :type password: string :param password: jira username password :type project: string :param project: jira project """ self.__jira_server = server self.__username = username self.__password = password self.jira_project = project self._init_jira_client() def _init_jira_client(self): options = {'server': self.__jira_server} def create_custom_field(fields=None): url = self._get_url('field') r = self._session.post(url, data=json.dumps(fields)) if r.status_code != 201: raise JIRAError(r.status_code, request=r) return r # Jira library doesn't have method for creating custom fields setattr(JIRA, 'create_custom_field', create_custom_field) self.jira_client = JIRA(options, basic_auth=(self.__username, self.__password)) def get_jira_projects(self): return self.jira_client.projects() def create_project(self, key, name, jira_type="Software"): return self.jira_client.create_project(key, name, jira_type) def get_jira_issue(self, report): """ Return Jira Issue based on HackerOne Report issue_tracker_reference_id :type report: h1.models.Report :param report: hackerone report :return: Jira Issue """ try: return self.jira_client.issue(report.issue_tracker_reference_id) except JIRAError as e: if e.text == "Issue Does Not Exist": return None else: raise @staticmethod def _get_jira_summary(report): return "%s - %s" % (report.id, report.title) def _get_jira_description(self, report): return self.DESCRIPTION % { 'description': report.vulnerability_information, 'reporter': report.reporter.name, 'assignee': report.assignee.name if report.assignee is not None else "", 'state': report.state, 'created': report.created_at, 'last_activity': report.last_activity_at, 'name': report.weakness.name, 'w_description': report.weakness.description, 'id': report.weakness.external_id, 'rating': report.severity.rating, 'score': report.severity.score } def create_jira_issue(self, report): """ Create Jira Issue https://developer.atlassian.com/server/jira/platform/jira-rest-api-example-create-issue-7897248/ :type report: h1.models.Report :param report: hackerone report :type :return: string :return: Jira ID """ issue_dict = { 'project': { 'key': self.jira_project }, 'summary': self._get_jira_summary(report), 'description': self._get_jira_description(report), 'issuetype': { 'name': 'Bug' }, 'labels': ['hackerOne'] } return self.jira_client.create_issue(fields=issue_dict, prefetch=True) def update_jira_issue(self, report, jira): fields = {} summary = self._get_jira_summary(report) if jira.fields.summary != summary: fields['summary'] = summary description = self._get_jira_description(report) if jira.fields.description != description: fields['description'] = description if fields: logging.info("Updating Existing Jira Issue: %s" % fields.keys()) jira.update(fields=fields) def search_for_jira_issues(self, report_id): """ Perform a Jira query search using JQL :param report_id: hacker one report id :return: returns jira issue match """ return self.jira_client.search_issues( '''project = %s AND summary ~ "%s"''' % (self.jira_project, report_id), maxResults=1) def get_fields(self): return self.jira_client.fields() def create_custom_field(self, fields): return self.jira_client.create_custom_field(fields) def get_remote_links(self, jira): return self.jira_client.remote_links(jira) def add_remote_link(self, report, jira, relationship="Relates"): links = set() # note all rmeote links have to have a global id for link in self.get_remote_links(jira): if hasattr(link, 'globalId'): links.add(link.globalId) if report.id not in links: destination = {'url': report.html_url, 'title': report.title} return self.jira_client.add_remote_link(jira, destination, report.id, self.APPLICATION, relationship) def add_simple_link(self, report, jira): """https://developer.atlassian.com/server/jira/platform/jira-rest-api-for-remote-issue-links/""" link = {'url': report.html_url, 'title': report.title} return self.jira_client.add_simple_link(jira, object=link) def add_jira_attachment(self, jira, attachment, filename): """Add H1 Attachment in Jira :param jira: Jira object that has attachments :param attachment: hacker one attachment object content :param filename: attachment file name :return: return """ return self.jira_client.add_attachment(issue=jira.id, attachment=attachment, filename=filename) def create_comments(self, jira, comment): return self.jira_client.add_comment(jira, comment)
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
class JiraRealBackend(JiraBaseBackend): """ NodeConductor interface to JIRA """ class Resource(object): """ Generic JIRA resource """ def __init__(self, manager): self.manager = manager class Issue(Resource): """ JIRA issues resource """ class IssueQuerySet(object): """ Issues queryset acceptable by django paginator """ def filter(self, term): if term: escaped_term = re.sub(r'([\^~*?\\:\(\)\[\]\{\}|!#&"+-])', r'\\\\\1', term) self.query_string = self.base_query_string + ' AND text ~ "%s"' % escaped_term return self def _fetch_items(self, offset=0, limit=1, force=False): # Default limit is 1 because this extra query required # only to determine the total number of items if hasattr(self, 'items') and not force: return self.items try: self.items = self.query_func( self.query_string, fields=self.fields, startAt=offset, maxResults=limit) except JIRAError as e: logger.exception( 'Failed to perform issues search with query "%s"', self.query_string) six.reraise(JiraBackendError, e) return self.items def __init__(self, jira, query_string, fields=None): self.fields = fields self.query_func = jira.search_issues self.query_string = self.base_query_string = query_string def __len__(self): return self._fetch_items().total def __iter__(self): return self._fetch_items() def __getitem__(self, val): return self._fetch_items(offset=val.start, limit=val.stop - val.start, force=True) def create(self, summary, description='', reporter='', assignee=None): args = { 'summary': summary, 'description': description, 'project': {'key': self.manager.core_project}, 'issuetype': {'name': self.manager.default_issue_type}, } # Validate reporter & assignee before actual issue creation if assignee: assignee = self.manager.users.get(assignee) if self.manager.reporter_field: args[self.manager.reporter_field_id] = reporter elif reporter: reporter = self.manager.users.get(reporter) try: issue = self.manager.jira.create_issue(fields=args) if reporter and not self.manager.reporter_field: issue.update(reporter={'name': reporter.name}) if assignee: self.manager.jira.assign_issue(issue, assignee.key) except JIRAError as e: logger.exception('Failed to create issue with summary "%s"', summary) six.reraise(JiraBackendError, e) return issue def get_by_user(self, username, user_key): try: issue = self.manager.jira.issue(user_key) except JIRAError: raise JiraBackendError("Can't find issue %s" % user_key) if self.manager.reporter_field: is_owner = getattr(issue.fields, self.manager.reporter_field_id) == username else: reporter = self.manager.users.get(username) is_owner = issue.fields.reporter.key == reporter.key if not is_owner: raise JiraBackendError("Access denied to issue %s for user %s" % (user_key, username)) return issue def list_by_user(self, username): if self.manager.reporter_field: query_string = "project = {} AND '{}' ~ '{}'".format( self.manager.core_project, self.manager.reporter_field, username) else: query_string = "project = {} AND reporter = {}".format( self.manager.core_project, username) query_string += " order by updated desc" return self.IssueQuerySet(self.manager.jira, query_string) class Comment(Resource): """ JIRA issue comments resource """ def list(self, issue_key): try: return self.manager.jira.comments(issue_key) except JIRAError as e: logger.exception( 'Failed to perform comments search for issue %s', issue_key) six.reraise(JiraBackendError, e) def create(self, issue_key, comment): return self.manager.jira.add_comment(issue_key, comment) class User(Resource): """ JIRA users resource """ def get(self, username): try: return self.manager.jira.user(username) except JIRAError: raise JiraBackendError("Unknown JIRA user %s" % username) def __init__(self, settings, core_project=None, reporter_field=None, default_issue_type='Task'): self.settings = settings self.core_project = core_project self.reporter_field = reporter_field self.default_issue_type = default_issue_type if settings.dummy: self.jira = JiraDummyClient() else: self.jira = JIRA( {'server': settings.backend_url, 'verify': False}, basic_auth=(settings.username, settings.password), validate=False) if self.reporter_field: try: self.reporter_field_id = next( f['id'] for f in self.jira.fields() if self.reporter_field in f['clauseNames']) except StopIteration: raise JiraBackendError("Can't custom field %s" % self.reporter_field) self.users = self.User(self) self.issues = self.Issue(self) self.comments = self.Comment(self)
from jira import JIRA # instantiate jira jira = JIRA(server='server', basic_auth=('userid', 'Password')) issues = jira.fields() jiracols = {} #print(issues) for i in issues: #print(i['name']+"_"+i['id']) jiracols[i['name']] = i['id'] print(jiracols) for k, v in jiracols.items(): print(k) print(v) print("NEXT!!")
class JiraDataStructuresManager(): ''' Extracts structural data from Jira for further analysis ''' options = None jira = None cfg = None def __init__(self): # By default, the client will connect to a JIRA instance started from the Atlassian Plugin SDK. # See https://developer.atlassian.com/display/DOCS/Installing+the+Atlassian+Plugin+SDK # for details. self.cfg = PropertiesHandler() self.options = {'server': __JIRA_SERVER__} self.jira = JIRA( self.options, basic_auth=(self.cfg.user, self.cfg.pwd)) # a username/password tuple def getAllSprintNames(self): '''get all boards names for internal use.''' boards = self.jira.boards() sprints = None csv = "Board Name; SprintName;State\n" for board in boards: try: sprints = self.jira.sprints(board.id) for s in sprints: csv += board.name + ";" + s.name + ";" + s.state + "\n" except Exception: pass return csv def getSprintListNamesAndState(self, boardName): '''It returns the project's associated sprints and status for its usage in other reports. ''' try: sprints = self.jira.sprints(boardName, False, 0, None, None) except Exception: # TODO: put the right error handling. return "Error: Scrum Board hasn't sprints associated." csv = "SprintName;State\n" for s in sprints: csv += s.name + ";" + s.state + "\n" return csv def getAllProjectsAsCSV(self): '''Returns all the projects names and codes as a long CSV string''' projects = self.jira.projects() csv = "Name;Code\n" for i in projects: csv += i.name + ";" + i.key + "\n" return csv def getCustomFieldID(self, name): '''Getting all the current custom fields ID's and dump it to a CSV file for revision.''' # Fetch all fields fileManager = FileManager() allfields = self.jira.fields() # Make a map from field name -> field id nameMap = {field['name']: field['id'] for field in allfields} stringBuffer = StringIO() stringBuffer.write("Field Name;Code\n") for field in allfields: stringBuffer.write(field['name'] + ";" + field['id'] + "\n") fileManager.getSendToCSVFile(stringBuffer.getvalue()) if (name != None): try: result = nameMap[name] except: return None return result else: return None
class ViraAPI(): ''' This class gets imported by __init__.py ''' def __init__(self): ''' Initialize vira ''' # Load user-defined config files file_servers = vim.eval('g:vira_config_file_servers') file_projects = vim.eval('g:vira_config_file_projects') try: self.vira_servers = load_config(file_servers) self.vira_projects = load_config(file_projects) except: print(f'Could not load {file_servers} or {file_projects}') self.userconfig_filter_default = { 'assignee': '', 'component': '', 'fixVersion': '', 'issuetype': '', 'priority': '', 'project': '', 'reporter': '', 'status': '', "'Epic Link'": '', 'statusCategory': ['To Do', 'In Progress'], 'text': '' } self.reset_filters() self.userconfig_newissue = { 'assignee': '', 'component': '', 'fixVersion': '', 'issuetype': 'Bug', 'priority': '', 'epics': '', 'status': '', } self.users = set() self.versions = set() self.servers = set() self.users_type = '' self.async_count = 0 self.versions_hide(True) def _async(self, func): try: func() except: pass def _async_vim(self): # TODO: VIRA-247 [210223] - Clean-up vim variables in python _async try: if len(vim.eval('s:versions')) == 0: vim.command('let s:projects = s:projects[1:]') if len(vim.eval('s:projects')) == 0: # TODO: VIRA-247 [210223] - Check for new projects and versions and start PRIORITY ranking for updates vim.command('let s:vira_async_timer = g:vira_async_timer') self.get_projects() self.get_versions() else: # self.version_percent(str(vim.eval('s:projects[0]')), str(vim.eval('s:versions[0]'))) vim.command('let s:versions = s:versions[1:]') if self.async_count == 0 and vim.eval( 's:vira_async_timer') == 10000: self.users = self.get_users() self.async_count = 1000 self.async_count -= 1 except: pass def create_issue(self, input_stripped): ''' Create new issue in jira ''' section = { 'summary': parse_prompt_text(input_stripped, '*Summary*', 'Description'), 'description': parse_prompt_text(input_stripped, 'Description', '*Project*'), 'project': parse_prompt_text(input_stripped, '*Project*', '*IssueType*'), 'issuetype': parse_prompt_text(input_stripped, '*IssueType*', 'Status'), 'status': parse_prompt_text(input_stripped, 'Status', 'Priority'), 'priority': parse_prompt_text(input_stripped, 'Priority', 'Component'), 'components': parse_prompt_text(input_stripped, 'Component', 'Version'), 'fixVersions': parse_prompt_text(input_stripped, 'Version', 'Assignee'), 'assignee': parse_prompt_text(input_stripped, 'Assignee'), } # Check if required fields was entered by user if section['summary'] == '' or section['project'] == '' or section[ 'issuetype'] == '': return issue_kwargs = { 'project': section['project'], 'summary': section['summary'], 'description': section['description'], 'issuetype': { 'name': section['issuetype'] }, 'priority': { 'name': section['priority'] }, 'components': [{ 'name': section['components'] }], 'fixVersions': [{ 'name': section['fixVersions'] }], 'assignee': { 'name': section['assignee'] }, } # Jira API doesn't accept empty fields for certain keys for key in issue_kwargs.copy().keys(): if section[key] == '': issue_kwargs.pop(key) # Create issue and transition issue_key = self.jira.create_issue(**issue_kwargs) if section['status'] != '': self.jira.transition_issue(issue_key, section['status']) jira_server = vim.eval('g:vira_serv') print(f'Added {jira_server}/browse/{issue_key}') def add_worklog(self, issue, timeSpentSeconds, comment): ''' Calculate the offset for the start time of the time tracking ''' earlier = datetime.now() - datetime.timedelta(seconds=timeSpentSeconds) self.jira.add_worklog(issue=issue, timeSpentSeconds=timeSpentSeconds, comment=comment, started=earlier) def connect(self, server): ''' Connect to Jira server with supplied auth details ''' self.users = set() self.versions = set() self.users_type = '' try: # Specify whether the server's TLS certificate needs to be verified if self.vira_servers[server].get('skip_cert_verify'): urllib3.disable_warnings( urllib3.exceptions.InsecureRequestWarning) cert_verify = False else: cert_verify = True # Get auth for current server username = self.vira_servers[server].get('username') password_cmd = self.vira_servers[server].get('password_cmd') if password_cmd: password = run_command(password_cmd)['stdout'].strip().split( '\n')[0] else: password = self.vira_servers[server]['password'] except: cert_verify = True server = vim.eval('input("server: ")') vim.command('let g:vira_serv = "' + server + '"') username = vim.eval('input("username: "******"password: "******"' + server + '"') # Authorize self.jira = JIRA(options={ 'server': server, 'verify': cert_verify, }, basic_auth=(username, password), timeout=2, async_=True, max_retries=2) # Initial list updates self.users = self.get_users() self.get_projects() self.get_versions() vim.command('echo "Connection to jira server was successful"') except JIRAError as e: if 'CAPTCHA' in str(e): vim.command( 'echo "Could not log into jira! Check authentication details and log in from web browser to enter mandatory CAPTCHA."' ) else: # vim.command('echo "' + str(e) + '"') vim.command('let g:vira_serv = ""') # raise e except: vim.command('let g:vira_serv = ""') vim.command( 'echo "Could not log into jira! See the README for vira_server.json information"' ) def filter_str(self, filterType): ''' Build a filter string to add to a JQL query The string will look similar to one of these: AND status in ('In Progress') AND status in ('In Progress', 'To Do') ''' if self.userconfig_filter.get(filterType, '') == '': return selection = str( self.userconfig_filter[filterType]).strip('[]') if type( self.userconfig_filter[filterType] ) == list else self.userconfig_filter[filterType] if type( self.userconfig_filter[filterType] ) == tuple else "'" + self.userconfig_filter[filterType] + "'" return str(f"{filterType} in ({selection})").replace( "'None'", "Null").replace("'Unassigned'", "Null").replace( "'currentUser'", "currentUser()").replace( "'currentUser()'", "currentUser()").replace( "'currentuser'", "currentUser()").replace( "'currentuser()'", "currentUser()").replace("'null'", "Null").replace( f"text in ({selection})", f"text ~ {selection}") def get_assign_issue(self): ''' Menu to select users ''' self.print_users() def get_assignees(self): ''' Get my issues with JQL ''' self.print_users() def get_comments(self, issue): ''' Get all the comments for an issue ''' # Get the issue requested issues = self.jira.search_issues('issue = "' + issue.key + '"', fields='summary,comment', json_result='True') # Loop through all of the comments comments = '' for comment in issues["issues"][0]["fields"]["comment"]["comments"]: comments += (f"{comment['author']['displayName']}" + ' | ', f"{comment['updated'][0:10]}" + ' @ ', f"{comment['updated'][11:16]}" + ' | ', f"{comment['body']} + '\n'") return comments def get_components(self): ''' Build a vim pop-up menu for a list of components ''' for component in self.jira.project_components( self.userconfig_filter['project']): print(component.name) print('None') def get_component(self): ''' Build a vim pop-up menu for a list of components ''' self.get_components() def get_epic(self): self.get_epics() def get_epics(self): ''' Get my issues with JQL ''' hold = dict(self.userconfig_filter) project = self.userconfig_filter['project'] self.reset_filters() self.userconfig_filter["issuetype"] = "Epic" self.userconfig_filter["project"] = project self.get_issues() print('None') self.userconfig_filter = hold def get_issue(self, issue): ''' Get single issue by issue id ''' return self.jira.issue(issue) def get_issues(self): ''' Get my issues with JQL ''' issues = [] key_length = 0 summary_length = 0 issuetype_length = 0 status_length = 4 user_length = 0 for issue in self.query_issues(): fields = issue['fields'] user = str(fields['assignee']['displayName']) if type( fields['assignee']) == dict else 'Unassigned' user_length = len(user) if len(user) > user_length else user_length key_length = len( issue['key']) if len(issue['key']) > key_length else key_length summary = fields['summary'] summary_length = len( summary) if len(summary) > summary_length else summary_length issuetype = fields['issuetype']['name'] issuetype_length = len(issuetype) if len( issuetype) > issuetype_length else issuetype_length status = fields['status']['name'] status_length = len( status) if len(status) > status_length else status_length issues.append([ issue['key'], fields['summary'], fields['issuetype']['name'], fields['status']['name'], user ]) # Add min/max limits on summary length columns = vim.eval("&columns") min_summary_length = 25 max_summary_length = int( columns) - key_length - issuetype_length - status_length - 28 summary_length = min_summary_length if max_summary_length < min_summary_length else max_summary_length if summary_length > max_summary_length else summary_length for issue in issues: print(('{: <' + str(key_length) + '}').format(issue[0]) + " │ " + ('{: <' + str(summary_length) + '}').format(issue[1][:summary_length]) + " │ " + ('{: <' + str(issuetype_length) + '}').format(issue[2]) + " │ " + ('{: <' + str(status_length) + '}').format(issue[3]) + ' │ ' + issue[4]) def get_issuetypes(self): ''' Get my issues with JQL ''' for issuetype in self.jira.issue_types(): print(issuetype) def get_issuetype(self): ''' Get my issues with JQL ''' for issuetype in self.jira.issue_types(): print(issuetype) def get_priorities(self): ''' Get my issues with JQL ''' for priority in self.jira.priorities(): print(priority) def print_projects(self): ''' Build a vim pop-up menu for a list of projects ''' all_projects = self.get_projects() batch_size = 10 project_batches = [ all_projects[i:i + batch_size] for i in range(0, len(all_projects), batch_size) ] for batch in project_batches: projects = self.jira.createmeta(projectKeys=','.join(batch), expand='projects')['projects'] [print(p['key'] + ' ~ ' + p['name']) for p in projects] def get_projects(self): ''' Build a vim pop-up menu for a list of projects ''' # Project filter for version list self.projects = [] for project in self.jira.projects(): self.projects.append(str(project)) vim.command('let s:projects = ' + str(self.projects)) return self.projects def get_priority(self): ''' Build a vim pop-up menu for a list of projects ''' self.get_priorities() def get_prompt_text(self, prompt_type, comment_id=None): ''' Get prompt text used for inputting text into jira ''' self.prompt_type = prompt_type # Edit filters if prompt_type == 'edit_filter': self.prompt_text_commented = '\n# Edit all filters in JSON format' self.prompt_text = json.dumps( self.userconfig_filter, indent=True) + self.prompt_text_commented return self.prompt_text # Edit summary active_issue = vim.eval("g:vira_active_issue") if prompt_type == 'summary': self.prompt_text_commented = '\n# Edit issue summary' summary = self.jira.search_issues( 'issue = "' + active_issue + '"', fields=','.join(['summary']), json_result='True')['issues'][0]['fields']['summary'] self.prompt_text = summary + self.prompt_text_commented return self.prompt_text # Edit description if prompt_type == 'description': self.prompt_text_commented = '\n# Edit issue description' description = self.jira.search_issues( 'issue = "' + active_issue + '"', fields=','.join(['description']), json_result='True')['issues'][0]['fields'].get('description') if description: description = description.replace('\r\n', '\n') else: description = '' self.prompt_text = description + self.prompt_text_commented return self.prompt_text self.prompt_text_commented = ''' # --------------------------------- # Please enter text above this line # An empty message will abort the operation. # # Below is a list of acceptable values for each input field. # # Users:''' for user in self.users: user = user.split(' ~ ') name = user[0] id = user[1] if self.users_type == 'accountId': self.prompt_text_commented += f''' # [{name}|~accountid:{id}]''' else: self.prompt_text_commented += f''' # [~{id}]''' # Add comment if self.prompt_type == 'add_comment': self.prompt_text = self.prompt_text_commented return self.prompt_text # Edit comment if self.prompt_type == 'edit_comment': self.active_comment = self.jira.comment(active_issue, comment_id) self.prompt_text = self.active_comment.body + self.prompt_text_commented return self.prompt_text statuses = [x.name for x in self.jira.statuses()] issuetypes = [x.name for x in self.jira.issue_types()] priorities = [x.name for x in self.jira.priorities()] components = [ x.name for x in self.jira.project_components( self.userconfig_filter['project']) ] if self.userconfig_filter['project'] != '' else '' versions = [ x.name for x in self.jira.project_versions( self.userconfig_filter['project']) ] if self.userconfig_filter['project'] != '' else '' projects = [x.key for x in self.jira.projects()] # Extra info for prompt_type == 'issue' self.prompt_text_commented += f''' # # Projects: {projects} # IssueTypes: {issuetypes} # Statuses: {statuses} # Priorities: {priorities} # Components in {self.userconfig_filter["project"]} Project: {components} # Versions in {self.userconfig_filter["project"]} Project: {versions}''' self.prompt_text = f'''[*Summary*] [Description] [*Project*] {self.userconfig_filter["project"]} [*IssueType*] {self.userconfig_newissue["issuetype"]} [Status] {self.userconfig_newissue["status"]} [Priority] {self.userconfig_newissue["priority"]} [Component] {self.userconfig_newissue["component"]} [Version] {self.userconfig_newissue["fixVersion"]} [Assignee] {self.userconfig_newissue["assignee"]} {self.prompt_text_commented}''' return self.prompt_text def format_date(self, date): time = datetime.now().strptime(date, '%Y-%m-%dT%H:%M:%S.%f%z').astimezone() return str(time)[0:10] + ' ' + str(time)[11:16] def get_report(self): ''' Print a report for the given issue ''' for customfield in self.jira.fields(): if customfield['name'] == 'Epic Link': epicID = customfield['id'] # Get passed issue content active_issue = vim.eval("g:vira_active_issue") issues = self.jira.search_issues( 'issue = "' + active_issue + '"', fields=','.join([ 'project', 'summary', 'comment', 'component', 'description', 'issuetype', 'priority', 'status', 'created', 'updated', 'assignee', 'reporter', 'fixVersion', 'customfield_10106', 'labels', epicID ]), json_result='True') issue = issues['issues'][0]['fields'] # Prepare report data open_fold = '{{{' close_fold = '}}}' summary = issue['summary'] story_points = str(issue.get('customfield_10106', '')) created = self.format_date(issue['created']) updated = self.format_date(issue['updated']) issuetype = issue['issuetype']['name'] status = issue['status']['name'] priority = issue['priority']['name'] assignee = issue['assignee']['displayName'] if type( issue['assignee']) == dict else 'Unassigned' reporter = issue['reporter']['displayName'] component = ', '.join([c['name'] for c in issue['components']]) version = ', '.join([v['name'] for v in issue['fixVersions']]) epics = str(issue.get(epicID)) vim.command(f'let s:vira_epic_field = "{epicID}"') description = str(issue.get('description')) # Version percent for single version attacted # if len(issue['fixVersions']) == 1 and version != '': # version += ' | ' + self.version_percent( # str(issue['project']['key']), version) + '%' comments = '' idx = 0 for idx, comment in enumerate((issue['comment']['comments'])): comments += ''.join([ comment['author']['displayName'] + ' @ ' + self.format_date(comment['updated']) + ' {{' + '{2\n' + comment['body'] + '\n}}}\n' ]) old_count = idx - 3 old_comment = 'Comment' if old_count == 1 else 'Comments' comments = ''.join( [str(old_count) + ' Older ' + old_comment + ' {{{1\n']) + comments if old_count >= 1 else comments comments = comments.replace('}}}', '}}}}}}', idx - 3) comments = comments.replace('}}}}}}', '}}}', idx - 4) # Find the length of the longest word [-1] words = [ created, updated, issuetype, status, story_points, priority, component, version, assignee, reporter, epics ] wordslength = sorted(words, key=len)[-1] s = '─' dashlength = s.join([char * len(wordslength) for char in s]) active_issue_spacing = int((16 + len(dashlength)) / 2 - len(active_issue) / 2) active_issue_spaces = ' '.join( [char * (active_issue_spacing) for char in ' ']) active_issue_space = ' '.join([ char * ((len(active_issue) + len(dashlength)) % 2) for char in ' ' ]) created_spaces = ' '.join( [char * (len(dashlength) - len(created)) for char in ' ']) updated_spaces = ' '.join( [char * (len(dashlength) - len(updated)) for char in ' ']) task_type_spaces = ' '.join( [char * (len(dashlength) - len(issuetype)) for char in ' ']) status_spaces = ' '.join( [char * (len(dashlength) - len(status)) for char in ' ']) story_points_spaces = ''.join( [char * (len(dashlength) - len(story_points)) for char in ' ']) priority_spaces = ''.join( [char * (len(dashlength) - len(priority)) for char in ' ']) component_spaces = ''.join( [char * (len(dashlength) - len(component)) for char in ' ']) version_spaces = ''.join( [char * (len(dashlength) - len(version)) for char in ' ']) assignee_spaces = ''.join( [char * (len(dashlength) - len(assignee)) for char in ' ']) reporter_spaces = ''.join( [char * (len(dashlength) - len(reporter)) for char in ' ']) epics_spaces = ''.join( [char * (len(dashlength) - len(epics)) for char in ' ']) # Create report template and fill with data report = '''┌────────────────{dashlength}─┐ │{active_issue_spaces}{active_issue}{active_issue_spaces}{active_issue_space} │ ├──────────────┬─{dashlength}─┤ │ Created │ {created}{created_spaces} │ │ Updated │ {updated}{updated_spaces} │ │ Type │ {issuetype}{task_type_spaces} │ │ Status │ {status}{status_spaces} │ │ Story Points │ {story_points}{story_points_spaces} │ │ Priority │ {priority}{priority_spaces} │ │ Epic Link │ {epics}{epics_spaces} │ │ Component(s) │ {component}{component_spaces} │ │ Version(s) │ {version}{version_spaces} │ │ Assignee │ {assignee}{assignee_spaces} │ │ Reporter │ {reporter}{reporter_spaces} │ └──────────────┴─{dashlength}─┘ ┌──────────────┐ │ Summary │ └──────────────┘ {summary} ┌──────────────┐ │ Description │ └──────────────┘ {description} ┌──────────────┐ │ Comments │ └──────────────┘ {comments}''' self.set_report_lines(report, description, issue) self.prompt_text = self.report_users(report.format(**locals())) return self.prompt_text def report_users(self, report): ''' Replace report accountid with names ''' for user in self.users: user = user.split(' ~ ') if user[0] != "Unassigned": report = report.replace('accountid:', '').replace('[~' + user[1] + ']', '[~' + user[0] + ']') return report def get_reporters(self): ''' Get my issues with JQL ''' self.print_users() def get_servers(self): ''' Get list of servers ''' try: for server in self.vira_servers.keys(): print(server) print('Null') except: self.connect('') def get_statuses(self): ''' Get my issues with JQL ''' statuses = [] for status in self.jira.statuses(): if str(status) not in statuses: statuses.append(str(status)) print(str(status)) def get_set_status(self): ''' Get my issues with JQL ''' self.get_statuses() def get_version(self): ''' Get my issues with JQL ''' self.print_versions() def new_component(self, name, project): ''' New component added to project ''' self.jira.create_component(name=name, project=project, description=name) def new_version(self, name, project, description): ''' Get my issues with JQL ''' self.jira.create_version(name=name, project=project, description=description) def print_users(self): ''' Print users ''' print(self.get_current_user() + ' ~ currentUser') for user in self.users: print(user) print('Unassigned') def get_users(self): ''' Get my issues with JQL ''' query = 'ORDER BY updated DESC' issues = self.jira.search_issues(query, fields='assignee, reporter', json_result='True', maxResults=-1) # Determine cloud/server jira self.users_type = 'accountId' if issues['issues'][0]['fields'][ 'reporter'].get('accountId') else 'name' for issue in issues['issues']: user = str(issue['fields']['reporter']['displayName'] ) + ' ~ ' + issue['fields']['reporter'][self.users_type] self.users.add(user) if type(issue['fields']['assignee']) == dict: user = str( issue['fields']['assignee']['displayName'] ) + ' ~ ' + issue['fields']['assignee'][self.users_type] self.users.add(user) return sorted(self.users) def get_current_user(self): query = 'reporter = currentUser() or assignee = currentUser()' issues = self.jira.search_issues(query, fields='assignee, reporter', json_result='True', maxResults=-1) issue = issues['issues'][0]['fields'] return str(issue['assignee'][self.users_type] if type( issue['assignee']) == dict else issue['reporter'][self.users_type] if type(issue['reporter']) == dict else 'Unassigned') def print_versions(self): ''' Print version list with project filters ''' try: versions = sorted(self.versions) wordslength = sorted(versions, key=len)[-1] s = ' ' dashlength = s.join([char * len(wordslength) for char in s]) for version in versions: print( version.split('|')[0] + ''.join([ char * (len(dashlength) - len(version)) for char in ' ' ]) + ' ' + version.split('|')[1] + ' ' + version.split('|')[2]) except: pass print('None') def version_percent(self, project, fixVersion): project = str(project) fixVersion = str(fixVersion) if str(project) != '[]' and str(project) != '' and str( fixVersion) != '[]' and str(fixVersion) != '': query = 'fixVersion = ' + fixVersion + ' AND project = "' + project + '"' issues = self.jira.search_issues(query, fields='fixVersion', json_result='True', maxResults=1) try: issue = issues['issues'][0]['fields']['fixVersions'][0] idx = issue['id'] total = self.jira.version_count_related_issues( idx)['issuesFixedCount'] pending = self.jira.version_count_unresolved_issues(idx) fixed = total - pending percent = str(round(fixed / total * 100, 1)) if total != 0 else 1 space = ''.join([char * (5 - len(percent)) for char in ' ']) name = fixVersion try: description = issue['description'] except: description = 'None' pass except: total = 0 pending = 0 fixed = total - pending percent = "0" space = ''.join([char * (5 - len(percent)) for char in ' ']) name = fixVersion description = '' pass version = str( str(name) + ' ~ ' + str(description) + '|' + str(fixed) + '/' + str(total) + space + '|' + str(percent) + '%') self.versions_hide = vim.eval('g:vira_version_hide') if fixed != total or total == 0 or not int( self.versions_hide) == 1: self.versions.add( str(project) + ' ~ ' + str(version.replace('\'', ''))) else: percent = 0 return percent def get_versions(self): ''' Build a vim pop-up menu for a list of versions with project filters ''' # Loop through each project and all versions within try: for v in reversed( self.jira.project_versions(vim.eval('s:projects[0]'))): vim.command('let s:versions = add(s:versions,\"' + str(v) + '\")') except: vim.command('let s:versions = []') def load_project_config(self, repo): ''' Load project configuration for the current git repo The current repo can either be determined by current files path or by the user setting g:vira_repo (part of :ViraLoadProject) For example, an entry in projects.yaml may be: vira: server: https://jira.tgall.ca project_name: VIRA ''' # Only proceed if projects file parsed successfully if not getattr(self, 'vira_projects', None): return # If current repo/folder doesn't exist, use __default__ project config if it exists if repo == '': repo = run_command( 'git rev-parse --show-toplevel')['stdout'].strip() if not self.vira_projects.get(repo): repo = repo.split('/')[-1] if not self.vira_projects.get(repo): repo = run_command('pwd')['stdout'].strip() if not self.vira_projects.get(repo): repo = repo.split('/')[-1] if not self.vira_projects.get(repo): repo = '__default__' if not self.vira_projects.get('__default__'): return # Set server server = self.vira_projects.get(repo, {}).get('server') if server: vim.command(f'let g:vira_serv = "{server}"') # Set user-defined filters for current project for key in self.userconfig_filter.keys(): value = self.vira_projects.get(repo, {}).get('filter', {}).get(key) if value: self.userconfig_filter[key] = value # Set user-defined new-issue defaults for current project for key in self.userconfig_newissue.keys(): value = self.vira_projects.get(repo, {}).get('newissue', {}).get(key) if value: self.userconfig_newissue[key] = value # Set user-defined issue sort options sort_order = self.vira_projects.get(repo, {}).get('issuesort', 'updated DESC') self.userconfig_issuesort = ', '.join(sort_order) if type( sort_order) == list else sort_order def query_issues(self): ''' Query issues based on current filters ''' q = [] for filterType in self.userconfig_filter.keys(): filter_str = self.filter_str(filterType) if filter_str: q.append(filter_str) query = ' AND '.join(q) + ' ORDER BY ' + self.userconfig_issuesort issues = self.jira.search_issues( query, fields='summary,comment,status,statusCategory,issuetype,assignee', json_result='True', maxResults=vim.eval('g:vira_issue_limit')) return issues['issues'] def reset_filters(self): ''' Reset filters to their default values ''' self.userconfig_filter = dict(self.userconfig_filter_default) def set_report_lines(self, report, description, issue): ''' Create dictionary for vira report that shows relationship between line numbers and fields to be edited ''' writable_fields = { 'Assignee': 'ViraSetAssignee', 'Component': 'ViraSetComponent', 'Priority': 'ViraSetPriority', 'Epic Link': 'ViraSetEpic', 'Status': 'ViraSetStatus', 'Type': 'ViraSetType', 'Version': 'ViraSetVersion', } self.report_lines = {} for idx, line in enumerate(report.split('\n')): for field, command in writable_fields.items(): if field in line: self.report_lines[idx + 1] = command continue for x in range(16, 21): self.report_lines[x] = 'ViraEditSummary' description_len = description.count('\n') + 3 for x in range(21, 23 + description_len): self.report_lines[x] = 'ViraEditDescription' offset = 2 if len(issue['comment']['comments']) > 4 else 1 comment_line = 25 + description_len + offset for comment in issue['comment']['comments']: comment_len = comment['body'].count('\n') + 3 for x in range(comment_line, comment_line + comment_len): self.report_lines[x] = 'ViraEditComment ' + comment['id'] comment_line = comment_line + comment_len def set_prompt_text(self): ''' Take the user prompt text and perform an action Usually, this involves writing to the jira server ''' # User input issue = vim.eval('g:vira_active_issue') userinput = vim.eval('g:vira_input_text') input_stripped = userinput.replace(self.prompt_text_commented.strip(), '').strip() # Check if anything was actually entered by user if input_stripped == '' or userinput.strip() == self.prompt_text.strip( ): print("No vira actions performed") return if self.prompt_type == 'edit_filter': self.userconfig_filter = json.loads(input_stripped) elif self.prompt_type == 'add_comment': self.jira.add_comment(issue, input_stripped) elif self.prompt_type == 'edit_comment': self.active_comment.update(body=input_stripped) elif self.prompt_type == 'summary': self.jira.issue(issue).update(summary=input_stripped) elif self.prompt_type == 'description': self.jira.issue(issue).update(description=input_stripped) elif self.prompt_type == 'issue': self.create_issue(input_stripped) def versions_hide(self, state): ''' Display and hide complete versions ''' if state is True or 1 or 'ture' or 'True': self.version_hide = True elif state is False or 0 or 'false' or 'False': self.version_hide = False else: self.version_hide = not self.version_hide
print("description : ", str(product['description'])) comps = bzapi.getcomponents(product['name']) if comps is not None: # description : print("components (-c) :") for comp in comps: print(" ", str(comp)) exit(0) # # JIRA # jira = JIRA('https://jira.iotivity.org') projects = jira.projects() resp = jira.fields() fmap = {} #fr i in resp: # print ( i.id, id.name) #pp.pprint (resp) for i in resp: field_name = i[u'name'].encode('ascii', 'ignore') field_id = i[u'id'].encode('ascii', 'ignore') fmap[field_name] = field_id #if args.verbose: # pprint.pprint(fmap) #print (projects) #allfields=jira.fields() #nameMap = {field['name']:field['id'] for field in allfields}
class JiraAnalysis(): def __init__(self, jira_url, jira_username, jira_token, jira_squad_labels): self.issue_cache = {} self.jira = JIRA(jira_url, basic_auth=(jira_username, jira_token)) self.jira_squad_labels = jira_squad_labels self.sprint_field = self.get_custom_field_key('Sprint') self.story_point_field = self.get_custom_field_key('Story Points') if self.sprint_field is None: raise Exception("Failed to find Sprint Field") if self.story_point_field is None: raise Exception("Failed to find Story Point Field") # Retrieve the custom field matching to a particular name since JIRA gives custom fields a random ID def get_custom_field_key(self, name): all_fields = self.jira.fields() for field in all_fields: if field['name'] == name: return field['key'] return None # Retrieve the squad for an issue - based on labels def get_squad(self, issue): for label in issue.fields.labels: for squad_label in self.jira_squad_labels: if squad_label.lower() == label.lower(): return squad_label return None # Get issue type, a bit weird since it's off of fields and needs to be converted to string def get_issue_type(self, issue): return issue.fields.issuetype.name.lower() # Get description from an issue def get_description(self, issue): return issue.fields.description # Get story points from an issue def get_story_points(self, issue): return get_or_float_zero(issue.fields, self.story_point_field) # Get the priority of an issue def get_priority(self, issue): for label in issue.fields.labels: # TODO: Update this for other formats # Take the first label found to avoid double counting if '2018:q1:' in label.lower() or '2018:q2' in label.lower(): try: return int(label.split(':')[-1]) except: return 100 # The Misc priority return 100 # For a set of issues get stats by priority def get_priority_stats(self, issues): priority_count = Counter() priority_story_points = defaultdict(float) no_priority_stories = [] for issue in issues: priority = self.get_priority(issue) if priority is not None: story_points = self.get_story_points(issue) priority_count.update([priority]) priority_story_points[priority] += float(story_points) else: no_priority_stories.append(issue) return priority_count, priority_story_points, no_priority_stories # Wrap the pagination code so user doesn't have to do it themselves def get_issues(self, query): if query in self.issue_cache: return self.issue_cache[query] all_issues = [] MAX_RESULTS = 100 issues = self.jira.search_issues(query, maxResults=MAX_RESULTS) total = issues.total all_issues.extend(list(issues)) if total > MAX_RESULTS: # Actually need to paginate for page in range(1, int(ceil(1.0 * total / MAX_RESULTS))): logger.info('Getting page %s', page) issues = self.jira.search_issues(query, maxResults=MAX_RESULTS, startAt=page * MAX_RESULTS) logger.info('Retrieved %s issues', len(issues)) all_issues.extend(list(issues)) logger.info('Total retrieved %s', len(all_issues)) self.issue_cache[query] = all_issues return all_issues # Clean up and write issues to a CSV def write_issues(self, start_date, end_date, fn): issues = self.get_issues(self.get_issue_query(start_date, end_date)) with open(fn, 'w') as f: w = csv.writer(f) w.writerow([ "ticket", "summary", "squad", "priority", "story_points", "assignee", "resolved_date", "type" ]) for issue in issues: w.writerow([ issue, issue.fields.summary.encode('utf-8'), self.get_squad(issue), self.get_priority(issue), self.get_story_points(issue), issue.fields.assignee if issue.fields.assignee else 'None', issue.fields.resolutiondate, self.get_issue_type(issue) ]) # Get all done stories and bugs between a date range def get_issue_query(self, start_date, end_date): return 'project = "TL" and status = Done and resolutiondate >= "' + start_date + '" and resolutiondate <= "' + end_date + '" AND type in ("story", "bug", "task", "spike", "access")' # Just get the list of words def get_descriptions_words(self, start_date, end_date): issues = self.get_issues(self.get_issue_query(start_date, end_date)) words = [] for issue in issues: desc = self.get_description(issue) if desc is not None: words.extend(desc.split(' ')) return words # Measure analytics per priority def analyze_priorities(self, start_date, end_date): issues = self.get_issues(self.get_issue_query(start_date, end_date)) priority_count, priority_story_points, no_priority_stories = self.get_priority_stats( issues) logger.info('Priority counts') logger.info(print_dict(priority_count, '\n')) logger.info('Priority story points') logger.info(print_dict(priority_story_points, '\n')) logger.info('No priorities') for issue in no_priority_stories: logger.info("\t %s %s", issue, issue.fields.summary) # Measrure # of sprints to do a story def analyze_sprint_lag(self, start_date, end_date): squad_sprint_counts = defaultdict(list) squad_sprint_story_point_sum = defaultdict(float) squad_story_point_sum = defaultdict(float) squad_bugs = defaultdict(int) issues = self.get_issues(self.get_issue_query(start_date, end_date)) for issue in issues: squad = self.get_squad(issue) try: num_sprints = len(getattr(issue.fields, self.sprint_field)) except: num_sprints = 0 story_points = get_or_float_zero(issue.fields, self.story_point_field) issue_type = self.get_issue_type(issue) # Has a squad and was actually done via sprint process if squad and num_sprints > 0 and issue_type == 'story': squad_sprint_counts[squad].append(num_sprints) if story_points > 0: squad_sprint_story_point_sum[ squad] += num_sprints * story_points squad_story_point_sum[squad] += story_points if issue_type == 'bug': squad_bugs[squad] += 1 logger.info('Squad\tSprint Lag\tSP Sprint Lag\tBugs') for squad, counts in squad_sprint_counts.items(): logger.info( '%s\t%s\t%s\t%s', squad, sum(counts) * 1.0 / len(counts), squad_sprint_story_point_sum[squad] / squad_story_point_sum[squad], squad_bugs[squad]) # Measure # of story points done per assignee def analyze_story_points(self, start_date, end_date): user_story_point_sum = Counter() user_data = {} issues = self.get_issues(self.get_issue_query(start_date, end_date)) for issue in issues: story_points = get_or_float_zero(issue.fields, self.story_point_field) assignee = str( issue.fields.assignee) if issue.fields.assignee else 'None' user_story_point_sum.update({assignee: int(story_points)}) if assignee not in user_data: user_data[assignee] = defaultdict(int) issue_type = self.get_issue_type(issue) user_data[assignee][issue_type + '_cnt'] += 1 user_data[assignee][issue_type + '_story_points'] += int(story_points) logger.info('User\tSP\tStories\tBugs') for user, story_points in user_story_point_sum.most_common(100): logger.info('%s\t%s\t%s', user, story_points, user_data[user])
#If build user is not in priority list else: #jira url server = 'https://example.atlassian.net' options = { 'server': server } jira = JIRA(options, basic_auth=(user,apikey)) #Jira Api authentication try: issue = jira.issue(JiraTicketId) #Fetch Ticket from ticket ID getassignee = issue.fields.assignee.displayName #Fetching assignee from ticket assign = unicodedata.normalize('NFKD', getassignee).encode('ascii','ignore') status = issue.fields.status ticketType = issue.fields.issuetype #Fetching Severity field by name allfields = jira.fields() nameMap = {jira.field['name']:jira.field['id'] for jira.field in allfields} getvalue = getattr(issue.fields, nameMap["Severity"]) except: print("{} Ticket ID is wrong or you don't have permission to see it.".format(stime)) #---------------fetching email of assignee ---------- def ExceljiraRead(): try: #mentioned excel should have only read permision in jenkin server df = pd.read_excel('publickeyssh.xlsx', sheet_name='public-ssh-keys-final') jiraDisplayName = df['Employee Name'] jmailID = df['Email address'] for i in range(0,len(jmailID)-1): if jiraDisplayName[i]==assign: jiramailid=jmailID[i]
class JiraService: def __init__(self): self.jira = JIRA(server=os.environ['JIRA_INSTANCE'], basic_auth=(os.environ['JIRA_USER'], os.environ['JIRA_PASSWORD'])) self.hipchat = HypChat(os.environ['HIPCHAT_TOKEN'], endpoint = "https://puppet.hipchat.com") # map of <field-id> -> <field-name>, used to make querying custom fields more readable. self.name_map = {field['id'] : field['name'] for field in self.jira.fields()} # applies the passed-in JQL filter and returns an array of tickets that match it def get_tickets(self, search_filter): def process_ticket(ticket): ticket_fields = self.get_fields(ticket) return { "ticket" : ticket.key, "status" : ticket_fields["Status"]["name"] } return [process_ticket(ticket) for ticket in self.jira.search_issues(search_filter)] # returns a dictionary with the following keys: # (1) involved_devs # Nested structure with the following keys: # -Everything that dev_info returns # -category (Either "Assignee", "Watcher", or "Reporter", in that order) # (2) team # Lists the team associated with the ticket # # NOTE: Some puppet employees might have their e-mails end in @puppetlabs.com instead of # @puppet.com -- if this happens, then the code should be modified to try both e-mails when # acquiring the dev info. # # TODO: What if ticket does not have a field? E.g. No team, no assignee, etc. Handle this # case! def ticket_info(self, ticket): def involved_dev_info(involved_dev, category): dev_info = self.dev_info("*****@*****.**" % involved_dev['key']) dev_info['category'] = category return dev_info ticket_info = self.get_fields(self.jira.issue(ticket)) ticket_watchers = self.jira.watchers(ticket) # calculate the involved devs (assignee, reporter) = tuple( [[involved_dev_info(ticket_info[dev_type], dev_type)] if ticket_info[dev_type] else [] for dev_type in ("Assignee", "Reporter")] ) watchers = [involved_dev_info(watcher.raw, "Watcher") for watcher in ticket_watchers.watchers] return { "involved_devs" : assignee + watchers + reporter, "team" : ticket_info["Team"]["value"] if ticket_info["Team"] else None } # returns a dictionary with the following keys: # (1) name # (2) email # (3) hipchat_alias def dev_info(self, dev_email): hipchat_info = self.hipchat.get_user(dev_email) return { 'name' : hipchat_info['name'], 'email' : dev_email, 'hipchat_alias' : hipchat_info['mention_name'] } # TODO: Make this private after everything's tested def get_fields(self, jira_issue): return {self.name_map[field_id] : field_value for field_id, field_value in jira_issue.raw['fields'].items()}