class Jira_Edit_Issue(API_Slack_Dialog): def __init__(self, issue_id, field): super().__init__() self.issue_id = issue_id self.field = field self.issue = None self.api_issues = API_Issues() self.submit_label = 'Save' def setup(self): self.callback_id = 'jira_edit_issue' self.title = 'Edit {0}'.format(self.issue_id) self.issue = self.api_issues.issue(self.issue_id) if not self.issue: self.add_element_text('ERROR - Issue not found: {0}'.format( self.issue_id)) else: key = "{0}::{1}".format(self.issue_id, self.field) value = self.issue.get(self.field) #todo: add different type of edit fields based on field name self.add_element_textarea(self.field, key, value) return self def render(self): return { "callback_id": self.callback_id, "title": self.title, "submit_label": self.submit_label, "notify_on_cancel": self.notify_on_cancel, "elements": self.elements }
def add_nodes_from_issue(self, issue_key): self.add_node(issue_key) # .expand_node(issue_key) from osbot_jira.api.API_Issues import API_Issues api_issues = API_Issues() issue = api_issues.issue(issue_key) if issue is None: print('no data received for key: {0}'.format(issue_key)) return self links = issue.get('Issue Links') if links: available_links = "{0}".format('\n'.join(list(set(links)))) available_links_key = "links_for_{0}".format(issue_key) self.add_node(available_links_key, available_links, color='#C0C0C0') self.add_link(issue_key, available_links_key) #print('handling {0} with {1} links'.format(issue_key, len(links))) for link_type, values in links.items(): if (self.link_types is not []) and (link_type not in self.link_types): #print('skipping links') continue link_type_key = '{0}::{1}'.format(link_type, issue_key) self.add_node(link_type_key, link_type, color='yellow') self.add_link(issue_key, link_type_key) if values: for link_key in values: link_issue = api_issues.issue(link_key) summary = "{0} \n{1}".format(link_issue.get('Summary'), link_key) self.add_node(link_key, summary) self.add_link( link_type_key, link_key, ) else: print('no links for {0}'.format(issue_key))
class Edit_UI(): def __init__(self, issue_id=None, issues=None): self.issue_id = issue_id self.issues = issues self.columns = [ 'Key', 'Summary', 'Issue Link', 'Description', 'Status' ] # 'Latest_Information' self.projects = ['TASK', 'OUTCOME', 'KEYRESULT', 'QUESTION'] self.issue_types = ['Task', 'Outcome', 'KeyResult', 'Question'] self.link_types = ['is delivered by', 'is parent of', 'uses'] self.qgrid_to_jira = QGrid_To_Jira(None) self.api_jira = self.qgrid_to_jira.api_jira self.api_jira_rest = self.qgrid_to_jira.api_jira_rest self.api_issues = API_Issues() def ui_add_issue(self): self.dropdown_project = widgets.Dropdown(options=self.projects, description='Project') self.dropdown_issue_type = widgets.Dropdown(options=self.issue_types, description='Issue Type') self.dropdown_link_type = widgets.Dropdown(options=self.link_types, description='Link Type') self.text_summary = widgets.Text(value='new_issue', description='Summary') self.button_create_issue = widgets.Button(description='Create Issue', icon='plus-square') def on_click_create_issue(b): self.on_create_issue() self.button_create_issue.on_click(on_click_create_issue) return widgets.HBox([ self.dropdown_project, self.dropdown_issue_type, self.dropdown_link_type, self.text_summary, self.button_create_issue ]) def ui_link_issue(self): self.add_link_dropdown_issue_type = widgets.Dropdown( options=self.link_types, description='Link Type') self.add_link_text_issue_id = widgets.Text(value='', description='Issue Id') self.add_link_button_create_issue = widgets.Button( description='Link Issue', icon='link') def on_click_link_issue(b): self.on_link_issue() self.add_link_button_create_issue.on_click(on_click_link_issue) return widgets.HBox([ self.add_link_dropdown_issue_type, self.add_link_text_issue_id, self.add_link_button_create_issue ]) def ui_load_issue(self): self.output_area = widgets.Output(layout={ 'border': '1px solid black', 'width': '1200px' }) self.button_status = widgets.Button(description='...', layout=widgets.Layout(width='50%')) self.text_issue_id = widgets.Text(value=self.issue_id, description='Issue Id:') self.button_load = widgets.Button(description='Load Data', icon='download') self.button_load.style.button_color = 'lightblue' def on_button_clicked(b): self.issues = None # to force data reload self.issue_id = self.text_issue_id.value self.button_status.style.button_color = 'orange' self.button_status.description = 'Loading Data from {0}'.format( self.issue_id) if self.create_grid(): self.button_status.description = 'Done' self.button_status.style.button_color = 'lightgreen' self.button_load.on_click(on_button_clicked) return widgets.HBox( [self.text_issue_id, self.button_load, self.button_status]) def ui_add_grid(self): def on_value_change_local(event, qgrid_widget): self.on_value_change(event, qgrid_widget) self.grid = qgrid.show_grid(pd.DataFrame([]), grid_options={'maxVisibleRows': 100}) self.grid.on('cell_edited', on_value_change_local) self.create_grid() return self.grid def show_ui(self, show_load_issue=True, show_add_issue=True, show_add_link=True, show_qgrid=True, show_output=True): items = [] if show_load_issue: items.append(self.ui_load_issue()) if show_add_link: items.append(self.ui_link_issue()) if show_add_issue: items.append(self.ui_add_issue()) if show_qgrid: items.append(self.ui_add_grid()) if show_output: items.append(self.output_area) self.vbox = widgets.VBox(items) return self.vbox def on_create_issue(self): qgrid_to_jira = QGrid_To_Jira(None) with self.output_area: project = self.dropdown_project.value issue_type = self.dropdown_issue_type.value link_type = self.dropdown_link_type.value summary = self.text_summary.value #status = '...' issue_id = self.issue_id print( f"Creating '{issue_type}' issue in project '{project}' with summary '{summary}', and link '{link_type}' to '{issue_id}'" ) try: # Create issue self.button_status.style.button_color = '#E59866' self.button_status.description = "Creating '{0}' issue in project '{1}'".format( issue_type, project) new_issue_id = qgrid_to_jira.api_jira.issue_create( project, summary, '', issue_type).key # Link issue self.button_status.style.button_color = 'orange' self.button_status.description = "Linking new issue '{0}' issue to '{1}'".format( new_issue_id, issue_id) qgrid_to_jira.api_jira.issue_add_link(issue_id, link_type, new_issue_id) # Update Grid self.button_status.style.button_color = 'darkorange' self.button_status.description = 'Adding new issue to grid' self.add_row_to_grid(new_issue_id, summary, link_type) self.button_status.description = 'all done' self.button_status.style.button_color = 'lightgreen' except Exception as error: self.button_status.description = 'Error creating issue or link (see output area' self.button_status.style.button_color = 'pink' with self.output_area: print("{0}".format(error)) def on_link_issue(self): from_key = self.issue_id link_type = self.add_link_dropdown_issue_type.value to_key = self.add_link_text_issue_id.value with self.output_area: try: self.api_jira.issue_add_link(from_key, link_type, to_key) self.button_status.style.button_color = 'lightgreen' self.button_status.description = 'Link added ok' summary = self.api_issues.issue(to_key).get( 'Summary') # get Summary value from ELK self.add_row_to_grid( to_key, summary, link_type) # add row to the bottom of the grid except Exception as error: print("Error: {0}".format(error)) self.button_status.style.button_color = 'pink' self.button_status.description = 'Link Error (double check that the issue id exists)' def on_value_change(self, event, qgrid_widget): key = event.get('index') field = event.get('column') value = event.get('new') old = event.get('old') with self.output_area: print( "updating field '{0}' with value '{1}' on issue '{2}'".format( field, value, key)) self.button_status.style.button_color = 'orange' qgrid_to_jira = QGrid_To_Jira(None) if field == 'Issue Link': with self.output_area: if type( old ) is not str: # todo: find better way to do this. 'old' value is an from pandas.core.series import Series , and the old value doesn't seem to be in there (self.grid.get_selected_df()[old.name].values[0] also didn't seem work) print( "Error: can't remove link issue when multiple entries exist the the same issue id" ) else: self.button_status.description = "Replacing Issue field {0} with {1}".format( old, value) result = qgrid_to_jira.jira_update_issue_link( self.issue_id, key, old, value) else: self.button_status.description = "Updating field {0} on {1}".format( field, key) result = qgrid_to_jira.jira_update_field(key, field, value) if result.get('status') == 'ok': self.button_status.style.button_color = 'lightgreen' self.button_status.description = 'Issue updated ok' else: self.button_status.style.button_color = 'pink' self.button_status.description = 'Update Error: {0}'.format( result.get('data')) def add_row_to_grid(self, issue_id, summary, link_type): columns = list(self.grid.df.columns) new_row = [('Key', issue_id)] for name in columns: value = '' if name == 'Summary': value = summary if name == 'Issue Link': value = link_type new_row.append((name, value)) self.grid.add_row(new_row) def create_grid(self): if self.issue_id: try: # self.graph = jira.graph_links(self.issue_id, 'all', 1) # self.df_graph = graph_table(self.graph,self.columns).fillna('') df_issues = self.get_issue_df() if len(df_issues) > 0: self.grid.df = self.get_issue_df() return True message = f"No links found for '{self.issue_id}'" except Exception as error: with self.output_area: print('error :{0}'.format(error)) message = 'Create Grid Error: {0}'.format(error) self.button_status.style.button_color = 'pink' self.button_status.description = message return False def get_issue_df(self): if self.issues is None: self.issues = self.get_issues() return pd.DataFrame(self.issues, columns=self.columns).set_index('Key').fillna('') def get_issues(self): root_issue = self.api_issues.issue(self.issue_id) if root_issue == {}: return [] issues = [root_issue] issue_links = root_issue.get('Issue Links') for link_type, linked_issues in issue_links.items(): for linked_issue in linked_issues: issue = self.api_issues.issue(linked_issue) #issue = self.api_jira_rest.issue(linked_issue) # much slower than using elk issue['Issue Link'] = link_type issues.append(issue) return issues
class Jp_Graph_Data: def __init__(self): self.lambda_graph = Lambda('osbot_jira.lambdas.graph') self.api_issues = API_Issues() self.gs_bot_jira = GS_Bot_Jira() self.graph_commands = Lambda_Graph_Commands() self.elk_to_slack = ELK_to_Slack() def lambda_invoke(self, params): result = self.lambda_graph.invoke({'params': params, 'data': {}}) return json_loads(result) def issue(self, issue_id): return self.api_issues.issue(issue_id) def issues(self, issue_id): return self.api_issues.issues(issue_id) def jira_links(self, source, depth=1): params = ['links', source, depth] return self.gs_bot_jira.cmd_links(params, save_graph=False) def jira_search(self, query): params = query.split(' ') query = self.elk_to_slack.get_search_query(params) return self.api_issues.search_using_lucene(query) def graph_expand(self, source, depth, link_types): params = [source, depth, link_types] return self.graph_commands.expand(params=params, save_graph=False) #data = json..invoke(params)) #nodes = data.get('nodes') #edges = data.get('edges') def draw_graph(self, graph): nodes = graph.nodes edges = graph.edges import networkx as nx import matplotlib.pyplot as plt size = 40 plt.figure(figsize=(size, size)) G = nx.DiGraph() for node in nodes: G.add_node(node) for edge in edges: G.add_edge(edge[0], edge[2], label=edge[1]) edge_labels = nx.get_edge_attributes(G, 'label') # pos = nx.spring_layout(G) # pos=nx.nx_pydot.graphviz_layout(G,prog='dot') pos = nx.nx_pydot.graphviz_layout(G) nx.draw_networkx_nodes(G, pos, node_shape='', node_size=100) nx.draw_networkx_labels(G, pos, font_size=16) nx.draw_networkx_edges(G, pos) nx.draw_networkx_edge_labels(G, pos=pos, edge_labels=edge_labels, font_size=10)
class Jira_View_Issue(): def __init__(self, issue_id=None, channel=None, team_id=None, event=None): self.issue_id = issue_id self.channel = channel self.team_id = team_id self.event = event self.api_issues = API_Issues() self.api_jira = API_Jira() self.issue = None self.action_id = 'Jira_View_Issue' self.slack_blocks = API_Slack_Blocks() # refactor this to base class def message_not_supported_action(self, action=''): return { "text": ':red_circle: Sorry, action not recognized : {0}'.format(action), "attachments": [], 'replace_original': False } def message_execution_error(self, error): return { "text": ':red_circle: Sorry, there was an error executing the requested action: {0}' .format(error), "attachments": [], 'replace_original': False } def handle_action(self, event): self.send_message('aaaa') action = Misc.array_pop(event.get('actions'), 0) action_value = Misc.get_value(action, 'value') try: target = getattr(self, action_value) except: channel = event.get('channel').get('id') team_id = event.get('team').get('id') # temp code (refactor when adding full support for blocks) text = ':point_right: message not recognised: {0}'.format(action) API_Slack_Blocks().set_text(text).send_message(channel, team_id) return self.message_not_supported_action( action) # event.get('original_message')) try: return target(event) except Exception as error: return self.message_execution_error(error) def load_issue(self, issue_id=None): if issue_id: self.issue_id = issue_id self.issue = self.api_issues.issue(self.issue_id) return self def create(self): self.load_issue() if self.issue: key = self.issue.get('Key') summary = self.issue.get('Summary') latest_info = self.issue.get('Latest_Information') description = self.issue.get('Description') issue_type = self.issue.get('Issue Type') jira_link = "https://glasswall.atlassian.net/browse/{0}".format( key) # todo: put the glasswall jira url in a site config value #key_link = "{0}/browse/{1}".format(self.api_issues.server_url(), key) add_layout = self.slack_blocks.add_layout_section #text = "*{0}*:\n<{1}|{2} - {3}>".format(issue_type,key_link,key, summary) text = ":point_right: *Issue*: <{1}|{2} - {3}>".format( issue_type, jira_link, key, summary) add_layout(self.issue_id).add_text(text).render() if latest_info: add_layout().add_text( '*Latest Info:* \n{0}'.format(latest_info)).render() if description: add_layout().add_text( '*Description:* \n{0}'.format(description)).render() actions_section = self.slack_blocks.add_layout_actions( self.action_id) footer_section = self.slack_blocks.add_layout_context() #actions_section.add_button('Edit Issue Field', self.issue_id) \ actions_section.add_button('View Links' , self.issue_id) \ .add_button('Screenshot' , self.issue_id) \ .add_button('Reload Issue' , self.issue_id) \ .add_button('Raw Issue Data' , self.issue_id) # .add_button('Change Status', self.issue_id) self.add_block_actions_with_transitions(self.slack_blocks) self.add_select_with_issue_links() self.add_block_edit_issue_field(self.slack_blocks) self.slack_blocks.add_text('*Actions*') actions_section.render() self.slack_blocks.add_divider() footer_items = [ 'Status: *{0}*'.format(self.issue.get('Status')), 'Rating: *{0}*'.format(self.issue.get('Rating')), 'Priority: *{0}*'.format(self.issue.get('Priority')), 'Issue Type: *{0}*'.format(self.issue.get('Issue Type')), 'Assignee: *{0}*'.format(self.issue.get('Assignee')), 'Labels: *{0}*'.format(self.issue.get('Labels')), 'Creator: *{0}*'.format(self.issue.get('Creator')), 'Created: *{0}*'.format( self.issue.get('Created').split('T').pop(0)), 'Updated: *{0}*'.format( self.issue.get('Updated').split('T').pop(0)) ] footer_section.add_texts(footer_items).render() #issue_data = "```{0}```".format(json.dumps(self.issue,indent=4)) #self.slack_blocks.add_layout_section().add_text(issue_data).render() #self.slack_ui.text = "<{0}|{1} - {2}>".format(key_link,key, summary) #self.add_overflow('abc', 'chose one' ,[('aaaa','aaaa_1'),('bbbb','bbbb_2')]) #self.add_button('Edit Issue') #self.add_button('An Replay') self.slack_blocks.add_attachment({ 'text': 'Issue *{0}* Status: `{1}`'.format(self.issue_id, self.issue.get('Status')), 'color': 'good' }) return True else: self.slack_blocks.add_layout_section().add_text( ':red_circle: Issue not found: `{0}`'.format( self.issue_id)).render() return False def send(self): if self.channel: result = self.slack_blocks.send_message(self.channel, self.team_id) if type(result) == dict and result.get('ok') is False: error_messages = result.get('response_metadata').get( 'messages') self.send_message( ':red_circle: Error in `Jira_View_Issue.send`; ```{0}```'. format(error_messages)) return result def create_and_send(self): if self.create(): self.send_message( ':point_right: *Loading data for issue: `{0}`* :point_left:'. format(self.issue_id)) return self.send() # def an_replay(self, event): # original_message = event.get('original_message') # return { # 'text' : "{0}".format(event), #original_message.get('text'), # 'attachments' : original_message.get('attachments'), # 'replace_original': True # } def add_select_with_issue_links(self): issue_links = self.issue.get('Issue Links') if issue_links: actions = self.slack_blocks.add_layout_actions( action_id='Jira_View_Issue') option_groups = [] size = 0 for issue_type, links in issue_links.items(): options = {} for link_issue_id in links: link_issue = self.api_issues.issue(link_issue_id) if link_issue: link_summary = link_issue.get('Summary') link_issue_type = link_issue.get('Issue Type') text = "{0} - {1}".format(link_issue_type, link_summary)[0:75] if options.get(link_issue_type) is None: options[link_issue_type] = [ ] # use this to sort by link_issue_type options[link_issue_type].append((text, link_issue_id)) #options.append((text , link_issue_id)) options_sorted = [] for key, values in options.items(): for value in values: options_sorted.append(value) size += 1 option_groups.append((issue_type, options_sorted)) self.slack_blocks.add_text( '*{0} Linked issues* (select to view)'.format(size)) actions.add_select( 'Issue Links', option_groups=option_groups) #, action_id='view_issue') return actions.render() def create_ui_actions_with_transitions(self, issue_id=None, current_status=None, show_intro=True): if issue_id: self.issue_id = issue_id view = API_Slack_Blocks() self.add_block_actions_with_transitions(view, current_status, show_intro) return view.send_message(self.channel, self.team_id) def add_block_actions_with_transitions(self, view, current_status=None, show_intro=True): if self.issue is None: self.issue = self.api_issues.issue(self.issue_id) if current_status is None: # this helps with the situation when the issue has just been updated but the data has not reached out to ELK current_status = self.issue.get('Status') transitions = self.api_jira.issue_next_transitions(self.issue_id) if show_intro: view.add_text("*Change issue status to*: (click to change)") if len(transitions) > 0: actions = view.add_layout_actions(action_id='Jira_View_Issue') for key, value in transitions.items(): if key != current_status: action_id = "transition_to::{0}".format(value) value = "{0}::{1}::{2}".format(self.issue_id, value, key) actions.add_button(key, value=value, action_id=action_id) return actions.render() view.add_text('...no Transitions available...') def create_ui_edit_issue_field(self): view = API_Slack_Blocks() self.add_block_edit_issue_field(view) return view.send_message(self.channel, self.team_id) def add_block_edit_issue_field(self, view): view.add_text("*Edit Issue Field:* (select to edit)".format( self.issue_id)) self.issue = self.api_issues.issue(self.issue_id) if self.issue: #fields = set(self.issue) fields = [ 'Summary', 'Description', 'Labels' #'Assignee','Description', 'Labels', 'Latest Information','Summary', #'Priority','Rating','Email', 'Slack ID','Image_Url' ] action_id = 'Jira_View_Issue::edit_field::{0}'.format( self.issue_id) view.add_select(action_id, 'Field to edit', fields) # callback methods def send_message(self, message): if self.channel: return slack_message(message, [], self.channel, self.team_id) else: return message def edit_field(self, action): try: selected_option = action.get('selected_option') field = selected_option.get('text').get('text') issue_id = action.get('action_id').split('::').pop(3) trigger_id = self.event.get('trigger_id') slack_dialog = Jira_Edit_Issue(issue_id, field).setup().render() from gw_bot.api.API_Slack import API_Slack API_Slack(self.channel, self.team_id).slack.dialog_open(trigger_id=trigger_id, dialog=slack_dialog) except Exception as error: self.send_message( ':red_circle: Error in edit_field: {0}'.format(error)) def issue_links(self, action): self.view_issue(action) def reload_issue(self, action): self.view_issue(action) def screenshot(self, action): issue_id = action.get('value') payload = { 'params': ['screenshot', issue_id], 'channel': self.channel, 'team_id': self.team_id } Lambda('osbot_jira.lambdas.jira').invoke_async(payload) def raw_issue_data(self, action): issue_id = action.get('value') issue = API_Issues().issue(issue_id) if issue: issue_data = "```{0}```".format(json.dumps(issue, indent=4)) return self.send_message(issue_data) def view_issue(self, action): issue_id = action.get('value') if issue_id is None: selected_option = action.get('selected_option') if selected_option: issue_id = selected_option.get('value') if issue_id: payload = { 'params': ['issue', issue_id], 'channel': self.channel, 'team_id': self.team_id } Lambda('osbot_jira.lambdas.jira').invoke_async(payload) else: self.send_message( ':red_circle: Error in View Issue, no issue id found in action :{0}' .format(action)) def view_links(self, action): try: issue_id = action.get('value') self.send_message( ':point_right: Viewing all links for issue: `{0}`'.format( issue_id)) payload = { 'params': ['links', issue_id, 'all', '1'], 'channel': self.channel, 'team_id': self.team_id } Lambda('osbot_jira.lambdas.jira').invoke_async(payload) except Exception as error: self.send_message( ':red_circle: Error in View Links for issue with id `{0}`: {1}' .format(issue_id, error)) def change_status(self, action): try: self.issue_id = action.get('value') self.create_ui_actions_with_transitions() except Exception as error: self.send_message( ':red_circle: Error in change_status: {0}'.format(error)) def edit_issue_field(self, action): try: self.issue_id = action.get('value') self.create_ui_edit_issue_field() except Exception as error: self.send_message( ':red_circle: Error in change_status: {0}'.format(error)) def transition_to(self, action): value_split = action.get('value').split('::') issue_id = Misc.array_pop(value_split, 0) transition_to = Misc.array_pop(value_split, 0) transition_name = Misc.array_pop(value_split, 0) try: self.api_jira.issue_transition_to_id(issue_id, transition_to) self.send_message( ':white_check_mark: Changed `{0}` status to: *{1}*. Here are the new transitions available ' .format(issue_id, transition_name)) self.create_ui_actions_with_transitions(issue_id, transition_name, show_intro=False) except Exception as error: self.send_message( ':red_circle: Error in transition_to: {0}'.format(error))
class Test_API_Issues(unittest.TestCase): def setUp(self): self.key = 'RISK-1515' self.api = API_Issues() def test_epic_issues(self): results = self.api.epic_issues('SEC-2024') assert len(list(results)) > 10 def test_issue(self): issue = self.api.issue(self.key) assert issue.get('Key') == self.key @unittest.skip('todo: fix: error on "if item[\'found\'] is False:" line') def test_issues(self): keys = ['RISK-1499', 'RISK-1496', 'AAA'] #result = self.api.elastic.es.mget(index='jira', # doc_type='item', # body={'ids': keys}) results = self.api.elastic().get_many(keys) #Dev.pprint(results) def test_issues_all(self): assert len(self.api.issues_all('jira')) > 2000 def test_issues_created_in_last(self): result = self.api.issues_created_in_last("3d") assert len(result) > 10 def test_issues_updated_in_last(self): result = self.api.issues_updated_in_last("3d") assert len(result) > 10 def test_issues_created_in_last_nnn(self): assert len(self.api.issues_created_in_last_seconds( 1 * 24 * 60 * 60)) > 0 assert len(self.api.issues_created_in_last_minutes(1 * 24 * 60)) > 0 assert len(self.api.issues_created_in_last_hours(1 * 24)) > 0 assert len(self.api.issues_created_in_last_days(1)) > 0 assert len(self.api.issues_created_in_last_weeks(1)) > 0 assert len(self.api.issues_created_in_last_months(1)) > 0 @unittest.skip('fails in CodeBuild') def test_create_issue_table(self): table = self.api.create_issue_table('RISK-424') table.save_tmp() #Dev.print(table.puml) def test_labels(self): result = self.api.labels() assert 'VULN-2160' in result['AWSrisk'] assert len(result) > 650 def test_link_types(self): result = self.api.link_types() assert len(result) > 100 def test_keys(self): keys = self.api.keys() assert 'VULN-10' in keys assert len(keys) > 15000 def test_search(self): assert len(self.api.search("aw", "Summary", 100)) > 0 assert len(self.api.search("R1", "Labels", 10)) > 0 assert self.api.search("GSP-95", "Key", 10).pop().get('Key') == "GSP-95" def test_search_using_lucene(self): query = 'Project:RISK AND Status:"Fixed"' results = self.api.search_using_lucene(query) assert len(results) > 200 # def test_stakeholders(self): # result = self.api.stakeholders() # #Dev.pprint(result) # #Dev.pprint(len(set(result))) ### move these to separate analysis file @unittest.skip('Fails in CodeBuild') def test_graph_issue_links_plant_uml(self): puml = self.api.graph_issue_links_plant_uml(self.key) puml.save_tmp() puml._dev_send_to_slack()