Skip to content

Commit 18cac73

Browse files
authored
Fix #256: Add components parameter (#275)
* Add components parameter * Add check about Jira components in heartbeat
1 parent 3a064fe commit 18cac73

File tree

7 files changed

+116
-5
lines changed

7 files changed

+116
-5
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ to the Bugzilla ticket on the Jira issue.
8383
- mapping [str, list[str]]
8484
- If defined, the specified steps are executed. The group of steps listed under `new` are executed when a Bugzilla event occurs on a ticket that is unknown to Jira. The steps under `existing`, when the Bugzilla ticket is already linked to a Jira issue. The steps under `comment` when a comment is posted on a linked Bugzilla ticket.
8585
If one of these groups is not specified, the default steps will be used.
86+
- `components` (optional)
87+
- list [str]
88+
- If defined, the created issues will be assigned the specified components.
8689
- `sync_whiteboard_labels` (optional)
8790
- boolean
8891
- Whether to sync the Bugzilla status whiteboard labels to Jira. Defaults to `true`.

config/config.local.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@
66
enabled: true
77
parameters:
88
jira_project_key: DevTest
9+
components:
10+
- "Main"

jbi/actions/steps.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ def create_comment(context: ActionContext, **parameters):
3131
def create_issue(context: ActionContext, **parameters):
3232
"""Create the Jira issue with the first comment as the description."""
3333
sync_whiteboard_labels: bool = parameters.get("sync_whiteboard_labels", True)
34+
components: list[str] = parameters.get("components", [])
3435
bug = context.bug
3536

3637
# In the payload of a bug creation, the `comment` field is `null`.
@@ -42,6 +43,7 @@ def create_issue(context: ActionContext, **parameters):
4243
context,
4344
description,
4445
sync_whiteboard_labels=sync_whiteboard_labels,
46+
components=components,
4547
)
4648
issue_key = jira_create_response.get("key")
4749

jbi/services/jira.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ def check_health(actions: Actions) -> ServiceHealth:
6767
"up": is_up,
6868
"all_projects_are_visible": is_up and _all_projects_visible(actions),
6969
"all_projects_have_permissions": _all_projects_permissions(actions),
70+
"all_projects_components_exist": is_up
71+
and _all_projects_components_exist(actions),
7072
}
7173
return health
7274

@@ -144,6 +146,28 @@ def _validate_permissions(all_projects_perms):
144146
return not misconfigured
145147

146148

149+
def _all_projects_components_exist(actions: Actions):
150+
components_by_project = {
151+
action.parameters["jira_project_key"]: action.parameters["components"]
152+
for action in actions
153+
if "components" in action.parameters
154+
}
155+
success = True
156+
for project, specified_components in components_by_project.items():
157+
all_project_components = get_client().get_project_components(project)
158+
all_components_names = set(comp["name"] for comp in all_project_components)
159+
unknown = set(specified_components) - all_components_names
160+
if unknown:
161+
logger.error(
162+
"Jira project %s does not have components %s",
163+
project,
164+
unknown,
165+
)
166+
success = False
167+
168+
return success
169+
170+
147171
class JiraCreateError(Exception):
148172
"""Error raised on Jira issue creation."""
149173

@@ -152,6 +176,7 @@ def create_jira_issue(
152176
context: ActionContext,
153177
description: str,
154178
sync_whiteboard_labels: bool,
179+
components: list[str],
155180
):
156181
"""Create a Jira issue with the specified fields and return its key."""
157182
bug = context.bug
@@ -169,7 +194,19 @@ def create_jira_issue(
169194
if sync_whiteboard_labels:
170195
fields["labels"] = bug.get_jira_labels()
171196

172-
jira_response_create = get_client().create_issue(fields=fields)
197+
client = get_client()
198+
199+
if components:
200+
# Fetch all projects components, and match their id by name.
201+
all_project_components = client.get_project_components(context.jira.project)
202+
components_id = [
203+
{"id": comp["id"]}
204+
for comp in all_project_components
205+
if comp["name"] in components
206+
]
207+
fields["components"] = components_id
208+
209+
jira_response_create = client.create_issue(fields=fields)
173210

174211
# Jira response can be of the form: List or Dictionary
175212
if isinstance(jira_response_create, list):

tests/conftest.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,13 @@ def mocked_bugzilla(request):
5353

5454

5555
@pytest.fixture(autouse=True)
56-
def mocked_jira():
57-
with mock.patch("jbi.services.jira.Jira") as mocked_jira:
58-
yield mocked_jira()
59-
jira.get_client.cache_clear()
56+
def mocked_jira(request):
57+
if "no_mocked_jira" in request.keywords:
58+
yield None
59+
else:
60+
with mock.patch("jbi.services.jira.Jira") as mocked_jira:
61+
yield mocked_jira()
62+
jira.get_client.cache_clear()
6063

6164

6265
@pytest.fixture

tests/unit/services/test_jira.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1+
import json
12
from unittest import mock
23

4+
import pytest
5+
import responses
6+
7+
from jbi.environment import get_settings
38
from jbi.services import jira
49

510

@@ -19,3 +24,43 @@ def test_timer_is_used_on_jira_create_issue():
1924
jira_client.create_issue({})
2025

2126
mocked.timer.assert_called_with("jbi.jira.methods.create_issue.timer")
27+
28+
29+
@pytest.mark.no_mocked_jira
30+
def test_create_issue_with_components(mocked_responses, context_create_example):
31+
url = f"{get_settings().jira_base_url}rest/api/2/project/{context_create_example.jira.project}/components"
32+
mocked_responses.add(
33+
responses.GET,
34+
url,
35+
json=[
36+
{
37+
"id": "10000",
38+
"name": "Component 1",
39+
},
40+
{
41+
"id": "42",
42+
"name": "Remote Settings",
43+
},
44+
],
45+
)
46+
47+
url = f"{get_settings().jira_base_url}rest/api/2/issue"
48+
mocked_responses.add(
49+
responses.POST,
50+
url,
51+
json={
52+
"id": "10000",
53+
"key": "ED-24",
54+
},
55+
)
56+
57+
jira.create_jira_issue(
58+
context_create_example,
59+
"Description",
60+
sync_whiteboard_labels=False,
61+
components=["Remote Settings"],
62+
)
63+
64+
posted_data = json.loads(mocked_responses.calls[-1].request.body)
65+
66+
assert posted_data["fields"]["components"] == [{"id": "42"}]

tests/unit/test_router.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ def test_read_heartbeat_all_services_fail(anon_client, mocked_jira, mocked_bugzi
151151
"up": False,
152152
"all_projects_are_visible": False,
153153
"all_projects_have_permissions": False,
154+
"all_projects_components_exist": False,
154155
},
155156
"bugzilla": {
156157
"up": False,
@@ -171,6 +172,7 @@ def test_read_heartbeat_jira_services_fails(anon_client, mocked_jira, mocked_bug
171172
"up": False,
172173
"all_projects_are_visible": False,
173174
"all_projects_have_permissions": False,
175+
"all_projects_components_exist": False,
174176
},
175177
"bugzilla": {
176178
"up": True,
@@ -194,6 +196,7 @@ def test_read_heartbeat_bugzilla_services_fails(
194196
"up": True,
195197
"all_projects_are_visible": True,
196198
"all_projects_have_permissions": False,
199+
"all_projects_components_exist": False,
197200
},
198201
"bugzilla": {
199202
"up": False,
@@ -206,6 +209,7 @@ def test_read_heartbeat_success(anon_client, mocked_jira, mocked_bugzilla):
206209
mocked_bugzilla.logged_in = True
207210
mocked_jira.get_server_info.return_value = {}
208211
mocked_jira.projects.return_value = [{"key": "DevTest"}]
212+
mocked_jira.get_project_components.return_value = [{"name": "Main"}]
209213
mocked_jira.get_permissions.return_value = {
210214
"permissions": {
211215
"ADD_COMMENTS": {"havePermission": True},
@@ -223,6 +227,7 @@ def test_read_heartbeat_success(anon_client, mocked_jira, mocked_bugzilla):
223227
"up": True,
224228
"all_projects_are_visible": True,
225229
"all_projects_have_permissions": True,
230+
"all_projects_components_exist": True,
226231
},
227232
"bugzilla": {
228233
"up": True,
@@ -243,6 +248,7 @@ def test_jira_heartbeat_visible_projects(anon_client, mocked_jira, mocked_bugzil
243248
"up": True,
244249
"all_projects_are_visible": False,
245250
"all_projects_have_permissions": False,
251+
"all_projects_components_exist": False,
246252
},
247253
"bugzilla": {
248254
"up": True,
@@ -254,6 +260,7 @@ def test_jira_heartbeat_missing_permissions(anon_client, mocked_jira, mocked_bug
254260
"""/__heartbeat__ fails if configured projects don't match."""
255261
mocked_bugzilla.logged_in = True
256262
mocked_jira.get_server_info.return_value = {}
263+
mocked_jira.get_project_components.return_value = [{"name": "Main"}]
257264
mocked_jira.get_project_permission_scheme.return_value = {
258265
"permissions": {
259266
"ADD_COMMENTS": {"havePermission": True},
@@ -271,18 +278,30 @@ def test_jira_heartbeat_missing_permissions(anon_client, mocked_jira, mocked_bug
271278
"up": True,
272279
"all_projects_are_visible": False,
273280
"all_projects_have_permissions": False,
281+
"all_projects_components_exist": True,
274282
},
275283
"bugzilla": {
276284
"up": True,
277285
},
278286
}
279287

280288

289+
def test_jira_heartbeat_unknown_components(anon_client, mocked_jira, mocked_bugzilla):
290+
mocked_bugzilla.logged_in = True
291+
mocked_jira.get_server_info.return_value = {}
292+
293+
resp = anon_client.get("/__heartbeat__")
294+
295+
assert resp.status_code == 503
296+
assert not resp.json()["jira"]["all_projects_components_exist"]
297+
298+
281299
def test_head_heartbeat(anon_client, mocked_jira, mocked_bugzilla):
282300
"""/__heartbeat__ support head requests"""
283301
mocked_bugzilla.logged_in = True
284302
mocked_jira.get_server_info.return_value = {}
285303
mocked_jira.projects.return_value = [{"key": "DevTest"}]
304+
mocked_jira.get_project_components.return_value = [{"name": "Main"}]
286305
mocked_jira.get_permissions.return_value = {
287306
"permissions": {
288307
"ADD_COMMENTS": {"havePermission": True},

0 commit comments

Comments
 (0)