Skip to content

Commit 18b1a7a

Browse files
authored
Fix #604: Add product prefix to components for maybe_update_components (#607)
* Fix #604: Add product prefix to components for `maybe_update_components` * Use config object as suggested by @grahamalama
1 parent e33f80c commit 18b1a7a

File tree

10 files changed

+176
-28
lines changed

10 files changed

+176
-28
lines changed

config/config.local.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@
66
parameters:
77
jira_project_key: DevTest
88
jira_components:
9-
- "Main"
9+
set_custom_components:
10+
- "Main"

config/config.nonprod.yaml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,8 @@
169169
parameters:
170170
jira_project_key: JB
171171
jira_components:
172-
- "Data Platform Infrastructure"
172+
set_custom_components:
173+
- "Data Platform Infrastructure"
173174
steps:
174175
new:
175176
- create_issue
@@ -210,7 +211,8 @@
210211
parameters:
211212
jira_project_key: JB
212213
jira_components:
213-
- "Data Quality"
214+
set_custom_components:
215+
- "Data Quality"
214216
steps:
215217
new:
216218
- create_issue

config/config.prod.yaml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,8 @@
234234
parameters:
235235
jira_project_key: SE
236236
jira_components:
237-
- "remote-settings"
237+
set_custom_components:
238+
- "remote-settings"
238239
steps:
239240
new:
240241
- create_issue
@@ -306,7 +307,8 @@
306307
parameters:
307308
jira_project_key: DENG
308309
jira_components:
309-
- "Data Platform Infrastructure"
310+
set_custom_components:
311+
- "Data Platform Infrastructure"
310312
steps:
311313
new:
312314
- create_issue
@@ -347,7 +349,8 @@
347349
parameters:
348350
jira_project_key: DENG
349351
jira_components:
350-
- "Data Quality"
352+
set_custom_components:
353+
- "Data Quality"
351354
steps:
352355
new:
353356
- create_issue

docs/actions.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,20 @@ Parameters are used by `step` functions to control what Bugzilla data is synced
4949
- 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.
5050
If one of these groups is not specified, the default steps will be used.
5151
- `jira_components` (optional)
52-
- list [str]
53-
- If defined, the created issues will be assigned the specified components.
52+
- object
53+
- Controls how Jira components are set on issues in the `maybe_update_components` step.
54+
- `use_bug_component` (optional)
55+
- bool
56+
- Set bug's component as issue component, eg. ``General`` (default `true`)
57+
- `use_bug_product` (optional)
58+
- bool
59+
- Set bug's product as issue component, eg. ``Core`` (default `false`)
60+
- `use_bug_component_with_product_prefix` (optional)
61+
- bool
62+
- Set bug's full component as issue component, eg. ``Core::General`` (default `false`)
63+
- `set_custom_components` (optional)
64+
- list[str]
65+
- If defined, the issues will be assigned the specified components (default `[]`)
5466
- `labels_brackets` (optional)
5567
- enum ["yes", "no", "both"]
5668
- Controls whether issue labels should have brackets or not in the `sync_whiteboard_labels` step (default: "no")

jbi/models.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,21 @@ class ActionSteps(BaseModel):
4242
]
4343

4444

45+
class JiraComponents(BaseModel):
46+
"""Controls how Jira components are set on issues in the `maybe_update_components` step."""
47+
48+
use_bug_component: bool = True
49+
use_bug_product: bool = False
50+
use_bug_component_with_product_prefix: bool = False
51+
set_custom_components: list[str] = []
52+
53+
4554
class ActionParams(BaseModel):
4655
"""Params passed to Action step functions"""
4756

4857
jira_project_key: str
4958
steps: ActionSteps = ActionSteps()
50-
jira_components: list[str] = []
59+
jira_components: JiraComponents = JiraComponents()
5160
labels_brackets: str = Field("no", enum=["yes", "no", "both"])
5261
status_map: dict[str, str] = {}
5362
resolution_map: dict[str, str] = {}
@@ -191,6 +200,14 @@ class BugzillaBug(BaseModel):
191200
assigned_to: Optional[str]
192201
comment: Optional[BugzillaWebhookComment]
193202

203+
@property
204+
def product_component(self) -> str:
205+
"""Return the component prefixed with the product
206+
as show in the Bugzilla UI (eg. ``Core::General``).
207+
"""
208+
result = self.product + "::" if self.product else ""
209+
return result + self.component if self.component else result
210+
194211
def is_assigned(self) -> bool:
195212
"""Return `true` if the bug is assigned to a user."""
196213
return self.assigned_to != "[email protected]"

jbi/services/jira.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ def _validate_permissions(all_projects_perms):
197197

198198
def _all_projects_components_exist(actions: Actions):
199199
components_by_project = {
200-
action.parameters.jira_project_key: action.parameters.jira_components
200+
action.parameters.jira_project_key: action.parameters.jira_components.set_custom_components
201201
for action in actions
202202
}
203203
success = True

jbi/steps.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -231,9 +231,16 @@ def maybe_update_components(context: ActionContext, parameters: ActionParams):
231231
"""
232232
Update the Jira issue components
233233
"""
234-
candidate_components = set(parameters.jira_components)
235-
if context.bug.component:
234+
candidate_components = set(parameters.jira_components.set_custom_components)
235+
if context.bug.component and parameters.jira_components.use_bug_component:
236236
candidate_components.add(context.bug.component)
237+
if context.bug.product and parameters.jira_components.use_bug_product:
238+
candidate_components.add(context.bug.product)
239+
if (
240+
context.bug.product_component
241+
and parameters.jira_components.use_bug_component_with_product_prefix
242+
):
243+
candidate_components.add(context.bug.product_component)
237244
client = jira.get_client()
238245

239246
# Fetch all projects components, and match their id by name.
@@ -247,8 +254,8 @@ def maybe_update_components(context: ActionContext, parameters: ActionParams):
247254
# Warn if some specified components are unknown
248255
if candidate_components:
249256
logger.warning(
250-
"Could not find components %s in project",
251-
candidate_components,
257+
"Could not find components %r in project",
258+
", ".join(sorted(candidate_components)),
252259
extra=context.dict(),
253260
)
254261

tests/unit/services/test_jira.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from requests.exceptions import ConnectionError
88

99
from jbi.environment import get_settings
10-
from jbi.models import Actions
10+
from jbi.models import Actions, JiraComponents
1111
from jbi.services import jira
1212

1313
pytestmark = pytest.mark.no_mocked_jira
@@ -126,7 +126,10 @@ def test_all_projects_components_exist(
126126
json=project_components,
127127
)
128128
action = action_factory(
129-
parameters={"jira_project_key": "ABC", "jira_components": jira_components}
129+
parameters={
130+
"jira_project_key": "ABC",
131+
"jira_components": JiraComponents(set_custom_components=jira_components),
132+
}
130133
)
131134
actions = Actions(__root__=[action])
132135
result = jira._all_projects_components_exist(actions)

tests/unit/test_models.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,20 @@ def test_extract_see_also(see_also, expected):
7070
assert bug.extract_from_see_also() == expected
7171

7272

73+
@pytest.mark.parametrize(
74+
"product,component,expected",
75+
[
76+
(None, None, ""),
77+
(None, "General", "General"),
78+
("Product", None, "Product::"),
79+
("Product", "General", "Product::General"),
80+
],
81+
)
82+
def test_product_component(product, component, expected):
83+
bug = bug_factory(product=product, component=component)
84+
assert bug.product_component == expected
85+
86+
7387
@pytest.mark.parametrize(
7488
"whiteboard",
7589
[

tests/unit/test_steps.py

Lines changed: 101 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from jbi import steps
88
from jbi.environment import get_settings
99
from jbi.errors import IncompleteStepError
10-
from jbi.models import ActionContext
10+
from jbi.models import ActionContext, JiraComponents
1111
from jbi.runner import Executor
1212
from jbi.services.jira import JiraCreateError
1313
from tests.conftest import action_params_factory
@@ -602,45 +602,129 @@ def test_change_to_unknown_resolution_with_resolution_map(
602602
@pytest.mark.parametrize(
603603
"project_components,bug_component,config_components,expected_jira_components,expected_logs",
604604
[
605+
# Default, only from bug.
605606
(
607+
# Jira project components
606608
[
607609
{
608610
"id": "10000",
609611
"name": "Component 1",
610612
},
611613
{
612614
"id": "42",
613-
"name": "Remote Settings",
615+
"name": "Toolbar",
614616
},
615617
],
618+
# Bug component
616619
"Toolbar",
617-
["Remote Settings"],
620+
# Config
621+
JiraComponents(),
622+
# Expected issue components
618623
[{"id": "42"}],
619-
["Could not find components {'Toolbar'} in project"],
624+
# Expected logs
625+
[],
620626
),
621-
# Without components in config
627+
# Only from config.
622628
(
629+
# Jira project components
623630
[
624631
{
625-
"id": "37",
626-
"name": "Toolbar",
632+
"id": "10000",
633+
"name": "Component 1",
634+
},
635+
{
636+
"id": "42",
637+
"name": "Remote Settings",
627638
},
628639
],
640+
# Bug component
629641
"Toolbar",
642+
# Config
643+
JiraComponents(
644+
use_bug_component=False, set_custom_components=["Remote Settings"]
645+
),
646+
# Expected issue components
647+
[{"id": "42"}],
648+
# Expected logs
630649
[],
631-
[{"id": "37"}],
650+
),
651+
# Using bug product.
652+
(
653+
# Jira project components
654+
[
655+
{
656+
"id": "10000",
657+
"name": "JBI",
658+
},
659+
{
660+
"id": "20000",
661+
"name": "Framework",
662+
},
663+
],
664+
# Bug component
665+
"Framework",
666+
JiraComponents(use_bug_product=True),
667+
# Expected issue components
668+
[{"id": "10000"}, {"id": "20000"}],
669+
# Expected logs
632670
[],
633671
),
672+
# Using bug prefixed component.
673+
(
674+
# Jira project components
675+
[
676+
{
677+
"id": "10000",
678+
"name": "JBI::",
679+
},
680+
{
681+
"id": "20000",
682+
"name": "General",
683+
},
684+
],
685+
# Bug component
686+
None,
687+
# Config
688+
JiraComponents(use_bug_component_with_product_prefix=True),
689+
# Expected issue components
690+
[{"id": "10000"}],
691+
# Expected logs
692+
[],
693+
),
694+
# Using bug prefixed component.
695+
(
696+
# Jira project components
697+
[
698+
{
699+
"id": "10000",
700+
"name": "JBI::General",
701+
},
702+
],
703+
# Bug component
704+
"General",
705+
# Config
706+
JiraComponents(use_bug_component_with_product_prefix=True),
707+
# Expected issue components
708+
[{"id": "10000"}],
709+
# Expected logs
710+
["Could not find components 'General' in project"],
711+
),
634712
# Without components in project
635713
(
714+
# Jira project component
636715
[],
716+
# Bug component
637717
"Toolbar",
718+
# Config
719+
JiraComponents(),
720+
# Expected issue components
638721
[],
639-
[],
640-
["Could not find components {'Toolbar'} in project"],
722+
# Expected logs
723+
["Could not find components 'Toolbar' in project"],
641724
),
642725
# With more than one in config
643726
(
727+
# Jira project component
644728
[
645729
{
646730
"id": "10000",
@@ -651,9 +735,13 @@ def test_change_to_unknown_resolution_with_resolution_map(
651735
"name": "Remote Settings",
652736
},
653737
],
738+
# Bug component
654739
None,
655-
["Search", "Remote Settings"],
740+
# Config
741+
JiraComponents(set_custom_components=["Search", "Remote Settings"]),
742+
# Expected issue components
656743
[{"id": "10000"}, {"id": "42"}],
744+
# Expected logs
657745
[],
658746
),
659747
],
@@ -711,7 +799,8 @@ def test_maybe_update_components_failing(
711799
action_params_factory,
712800
):
713801
mocked_jira.get_project_components.return_value = [
714-
{"id": 1, "name": context_update_example.bug.component}
802+
{"id": 1, "name": context_update_example.bug.component},
803+
{"id": 2, "name": context_update_example.bug.product_component},
715804
]
716805
mocked_jira.update_issue_field.side_effect = requests.exceptions.HTTPError(
717806
"Field 'components' cannot be set", response=mock.MagicMock(status_code=400)

0 commit comments

Comments
 (0)