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()
def cmd_created_in_last(self, params, team_id=None, channel=None): elk_to_slack = ELK_to_Slack() if len(params) < 2: text = ":exclamation: you must provide an start date. You can use `1d`,`1w`,`1y` (d=day, w=week, y=year" return {"text": text, "attachments": []} from_date = params.pop() issues = API_Issues().issues_created_in_last(from_date) issues_text = elk_to_slack.get_text_with_issues_key_and_summary(issues) graph_name = elk_to_slack.save_issues_as_new_graph(issues) text = elk_to_slack.get_slack_message(issues, graph_name) max_table = 100 if len(issues) < max_table: text += "\n (Since there are less than {0} results also showing table with data)".format( max_table) self.cmd_table(["table", graph_name], team_id, channel) return { "text": text, "attachments": [{ 'text': issues_text, 'color': 'good' }] }
def cmd_created_between(self, params, team_id=None, channel=None): elk_to_slack = ELK_to_Slack() if len(params) < 3: text = ":exclamation: you must provide an start and end date. You can use `1d`,`1w`,`1y` (d=day, w=week, y=year" text += "\nTry `now-1d` and `now`" return {"text": text, "attachments": []} to_date = params.pop() from_date = params.pop() try: issues = API_Issues().elastic().get_data_between_dates( "Created", from_date, to_date) issues_text = elk_to_slack.get_text_with_issues_key_and_summary( issues) graph_name = elk_to_slack.save_issues_as_new_graph(issues) text = elk_to_slack.get_slack_message(issues, graph_name) min_table = 100 max_table = 100 if min_table < len(issues) < max_table: text += "\n (Since there are less than {0} (and more than {1}) results also showing table with data)".format( max_table, min_table) self.cmd_table(["table", graph_name], team_id, channel) return { "text": text, "attachments": [{ 'text': issues_text, 'color': 'good' }] } except Exception as error: text = "Error in cmd_created_between: {0}".format(error) return {"text": text, "attachments": []}
def raw_data(team_id=None, channel=None, params=None, data=None): data = None text = None attachments = [] if len(params) < 1: text = ':red_circle: Hi, for the `data` command, you need to provide a graph name' else: graph_name = params.pop(0) graph = Lambda_Graph().get_gs_graph___by_name(graph_name) if graph: data = { 'graph_name': graph_name, 'nodes': graph.nodes, 'edges': graph.edges } if len(params) == 1 and params.pop(0) == 'details': data['nodes'] = graph.get_nodes_issues() s3_bucket = 'gw-bot-lambdas' import tempfile with tempfile.NamedTemporaryFile(suffix='.json') as temp: temp.write(str.encode(json.dumps(data))) temp.flush() data = S3().file_upload_as_temp_file( temp.name, s3_bucket) attachments = [{ "color": "good", "text": "{0}".format(pprint.pformat(data)) }] else: from osbot_jira.api.API_Issues import API_Issues # if a graph wasn't found try to get the issue with that name issue = API_Issues().issue(graph_name) if issue: data = { 'nodes': { graph_name: issue }, # return as the first node 'edges': [] # with no edges } else: text = ':red_circle: Graph or issue with name `{0}` not found! Use the command `graph last` to see a list of the latest graphs generated'.format( graph_name) slack_message(text, attachments, channel, team_id) return data
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)
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))
def setUp(self): self.key = 'RISK-1515' self.api = API_Issues()
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()
def issue(key): from osbot_jira.api.API_Issues import API_Issues return API_Issues().issue(key)