Skip to content

Commit 995b91a

Browse files
authored
Merge pull request #1417 from OlehPalanskyi/master
Updating the Jira alert for scenarios where we need to create subtask (child issues) within existing tasks.
2 parents d687419 + 95bff8c commit 995b91a

File tree

4 files changed

+93
-4
lines changed

4 files changed

+93
-4
lines changed

CHANGELOG.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
- Renamed PR #1193's `fields` common rule option to `include_fields` due to collision with `new_term` rule type's existing `field` parameter - [#1408](https://github.com/jertel/elastalert2/pull/1408) - @jertel
55

66
## New features
7-
- None
7+
- [Jira] Add ability to create a subtask, etc of an existing parent - [#1417](https://github.com/jertel/elastalert2/pull/1417) - @olehpalanskyi
88

99
## Other changes
1010
- [workwechat] add workwechat msgtype - [#1369](https://github.com/jertel/elastalert2/pull/1369) - @bitqiu

docs/source/alerts.rst

+20
Original file line numberDiff line numberDiff line change
@@ -1195,6 +1195,26 @@ This alert requires four additional options:
11951195

11961196
Optional:
11971197

1198+
``jira_parent``: Specify an existing ticket that will be used as a parent to create a new subtask in it
1199+
1200+
For example, if you have this issue hierarchy:
1201+
Epic
1202+
Story, Task, Bug
1203+
Subtask
1204+
1205+
Then:
1206+
As a parent issue, an epic can have stories, tasks, and bugs as subtask (child issues).
1207+
As a parent issues, task, stories and bugs can have subtasks as subtask (child issues).
1208+
A subtask can’t have any subtask (child issues).
1209+
1210+
Example usage::
1211+
1212+
jira_server: "https://example.atlassian.net/"
1213+
jira_project: "XXX"
1214+
jira_assignee: user@example.com
1215+
jira_issuetype: "Sub-task"
1216+
jira_parent: "XXX-3164"
1217+
11981218
``jira_assignee``: Assigns an issue to a user.
11991219

12001220
``jira_component``: The name of the component or components to set the ticket to. This can be a single string or a list of strings. This is provided for backwards compatibility and will eventually be deprecated. It is preferable to use the plural ``jira_components`` instead.

elastalert/alerters/jira.py

+11-3
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ class JiraAlerter(Alerter):
4646
'jira_server',
4747
'jira_transition_to',
4848
'jira_watchers',
49+
'jira_parent'
4950
]
5051

5152
# Some built-in Jira types that can be used as custom fields require special handling
@@ -66,6 +67,8 @@ def __init__(self, rule):
6667
self.get_account(self.rule['jira_account_file'])
6768
self.project = self.rule['jira_project']
6869
self.issue_type = self.rule['jira_issuetype']
70+
# this parameter use to create subtasks
71+
self.parent = self.rule.get('jira_parent', None)
6972

7073
# Deferred settings refer to values that can only be resolved when a match
7174
# is found and as such loading them will be delayed until we find a match
@@ -126,8 +129,13 @@ def set_priority(self):
126129
elastalert_logger.error("Priority %s not found. Valid priorities are %s" % (self.priority, list(self.priority_ids.keys())))
127130

128131
def reset_jira_args(self):
129-
self.jira_args = {'project': {'key': self.project},
130-
'issuetype': {'name': self.issue_type}}
132+
if self.parent:
133+
self.jira_args = {'project': {'key': self.project},
134+
'parent': {'key': self.parent},
135+
'issuetype': {'name': self.issue_type}}
136+
else:
137+
self.jira_args = {'project': {'key': self.project},
138+
'issuetype': {'name': self.issue_type}}
131139

132140
if self.components:
133141
# Support single component or list
@@ -411,4 +419,4 @@ def get_account(self, account_file):
411419
self.apikey = account_conf['apikey']
412420
else:
413421
self.user = account_conf['user']
414-
self.password = account_conf['password']
422+
self.password = account_conf['password']

tests/alerters/jira_test.py

+61
Original file line numberDiff line numberDiff line change
@@ -429,3 +429,64 @@ def test_jira_auth_token(caplog):
429429
]
430430
# we only want to test authentication via apikey, the rest we don't care of
431431
assert mock_jira.mock_calls[:1] == expected
432+
433+
434+
def test_create_subtask(caplog):
435+
caplog.set_level(logging.INFO)
436+
description_txt = "Description stuff goes here like a runbook link."
437+
rule = {
438+
'name': 'test alert',
439+
'jira_account_file': 'jirafile',
440+
'type': mock_rule(),
441+
'jira_project': 'testproject',
442+
'jira_priority': 0,
443+
'jira_issuetype': 'testtype',
444+
'jira_parent': 'PARENT-123', # ID of the parent issue
445+
'jira_server': 'jiraserver',
446+
'jira_label': 'testlabel',
447+
'jira_component': 'testcomponent',
448+
'jira_description': description_txt,
449+
'jira_assignee': 'testuser',
450+
'jira_watchers': ['testwatcher1', 'testwatcher2'],
451+
'timestamp_field': '@timestamp',
452+
'alert_subject': 'Issue {0} occurred at {1}',
453+
'alert_subject_args': ['test_term', '@timestamp'],
454+
'rule_file': '/tmp/foo.yaml',
455+
}
456+
457+
mock_priority = mock.Mock(id='5')
458+
459+
with mock.patch('elastalert.alerters.jira.JIRA') as mock_jira, \
460+
mock.patch('elastalert.alerters.jira.read_yaml') as mock_open:
461+
mock_open.return_value = {'user': 'jirauser', 'password': 'jirapassword'}
462+
mock_jira.return_value.priorities.return_value = [mock_priority]
463+
mock_jira.return_value.fields.return_value = []
464+
alert = JiraAlerter(rule)
465+
alert.alert([{'test_term': 'test_value', '@timestamp': '2014-10-31T00:00:00'}])
466+
467+
expected = [
468+
mock.call('jiraserver', basic_auth=('jirauser', 'jirapassword')),
469+
mock.call().priorities(),
470+
mock.call().fields(),
471+
mock.call().create_issue(
472+
issuetype={'name': 'testtype'},
473+
priority={'id': '5'},
474+
project={'key': 'testproject'},
475+
labels=['testlabel'],
476+
components=[{'name': 'testcomponent'}],
477+
description=mock.ANY,
478+
summary='Issue test_value occurred at 2014-10-31T00:00:00',
479+
parent={'key': 'PARENT-123'} # Asserting that subtask is created with correct parent
480+
),
481+
mock.call().assign_issue(mock.ANY, 'testuser'),
482+
mock.call().add_watcher(mock.ANY, 'testwatcher1'),
483+
mock.call().add_watcher(mock.ANY, 'testwatcher2'),
484+
]
485+
# We don't care about additional calls to mock_jira, such as __str__
486+
# we only want to test creating subtasks, the rest we don't care of
487+
assert mock_jira.mock_calls[:7] == expected
488+
assert mock_jira.mock_calls[3][2]['description'].startswith(description_txt)
489+
user, level, message = caplog.record_tuples[0]
490+
assert 'elastalert' == user
491+
assert logging.INFO == level
492+
assert 'pened Jira ticket:' in message

0 commit comments

Comments
 (0)