diff --git a/config/config.prod.yaml b/config/config.prod.yaml index fc166d5f..ff5224d4 100644 --- a/config/config.prod.yaml +++ b/config/config.prod.yaml @@ -844,8 +844,9 @@ parameters: jira_project_key: BZFFX jira_components: - set_custom_components: - - "Firefox" + use_bug_component_with_product_prefix: true + use_bug_component: false + create_components: true steps: new: - create_issue @@ -885,8 +886,9 @@ parameters: jira_project_key: BZFFX jira_components: - set_custom_components: - - "Core" + use_bug_component_with_product_prefix: true + use_bug_component: false + create_components: true steps: new: - create_issue diff --git a/jbi/jira/client.py b/jbi/jira/client.py index 607c11b8..9c1155fe 100644 --- a/jbi/jira/client.py +++ b/jbi/jira/client.py @@ -70,6 +70,7 @@ def raise_for_status(self, *args, **kwargs): get_server_info = instrumented_method(Jira.get_server_info) get_project_components = instrumented_method(Jira.get_project_components) + create_component = instrumented_method(Jira.create_component) update_issue = instrumented_method(Jira.update_issue) update_issue_field = instrumented_method(Jira.update_issue_field) set_issue_status = instrumented_method(Jira.set_issue_status) diff --git a/jbi/jira/service.py b/jbi/jira/service.py index 4bac72d3..504d8649 100644 --- a/jbi/jira/service.py +++ b/jbi/jira/service.py @@ -350,6 +350,7 @@ def update_issue_components( self, context: ActionContext, components: Iterable[str], + create_components: bool = False, ) -> tuple[Optional[dict], set]: """Attempt to add components to the specified issue @@ -374,7 +375,16 @@ def update_issue_components( missing_components.remove(comp["name"]) if not jira_components: - return None, missing_components + if not create_components: + return None, missing_components + + for comp_name in missing_components: + new_comp = self.client.create_component( + project_key=context.jira.project, + name=comp_name, + ) + jira_components.append({"id": new_comp["id"]}) + missing_components.clear() resp = self.update_issue_field( context, field="components", value=jira_components diff --git a/jbi/models.py b/jbi/models.py index 2c078e6f..a22c7c80 100644 --- a/jbi/models.py +++ b/jbi/models.py @@ -80,6 +80,7 @@ class JiraComponents(BaseModel, frozen=True): use_bug_component: bool = True use_bug_product: bool = False use_bug_component_with_product_prefix: bool = False + create_components: bool = False set_custom_components: list[str] = [] diff --git a/jbi/steps.py b/jbi/steps.py index 75f6e20f..3bce543e 100644 --- a/jbi/steps.py +++ b/jbi/steps.py @@ -405,6 +405,7 @@ def maybe_update_components( resp, missing_components = jira_service.update_issue_components( context=context, components=candidate_components, + create_components=parameters.jira_components.create_components, ) except requests_exceptions.HTTPError as exc: if getattr(exc.response, "status_code", None) != 400: diff --git a/tests/unit/test_steps.py b/tests/unit/test_steps.py index ea863663..2d52d88e 100644 --- a/tests/unit/test_steps.py +++ b/tests/unit/test_steps.py @@ -1351,6 +1351,82 @@ def test_maybe_update_components_failing( ] +def test_maybe_update_components_create_components_normal_component( + action_context_factory, + mocked_jira, + action_params_factory, +): + action_context = action_context_factory( + operation=Operation.CREATE, + current_step="maybe_update_components", + bug__component="NewComponent", + jira__issue="JBI-234", + ) + action_params = action_params_factory( + jira_project_key=action_context.jira.project, + jira_components=JiraComponents(create_components=True), + ) + mocked_jira.get_project_components.return_value = [ + {"id": 1, "name": "ExistingComponent"}, + ] + mocked_jira.create_component.return_value = {"id": "42", "name": "NewComponent"} + + steps.maybe_update_components( + action_context, + parameters=action_params, + jira_service=JiraService(mocked_jira), + ) + + mocked_jira.create_component.assert_called_once_with( + project_key=action_context.jira.project, + name="NewComponent", + ) + mocked_jira.update_issue_field.assert_called_with( + key="JBI-234", + fields={"components": [{"id": "42"}]}, + ) + + +def test_maybe_update_components_create_components_prefix_component( + action_context_factory, + mocked_jira, + action_params_factory, +): + action_context = action_context_factory( + operation=Operation.CREATE, + current_step="maybe_update_components", + bug__product="Firefox", + bug__component="NewComponent", + jira__issue="JBI-234", + ) + action_params = action_params_factory( + jira_project_key=action_context.jira.project, + jira_components=JiraComponents( + create_components=True, + use_bug_component_with_product_prefix=True, + use_bug_component=False + ), + ) + mocked_jira.get_project_components.return_value = [ + {"id": 1, "name": "Firefox::ExistingComponent"}, + ] + mocked_jira.create_component.return_value = {"id": "42", "name": "Firefox::NewComponent"} + + steps.maybe_update_components( + action_context, + parameters=action_params, + jira_service=JiraService(mocked_jira), + ) + + mocked_jira.create_component.assert_called_once_with( + project_key=action_context.jira.project, + name="Firefox::NewComponent", + ) + mocked_jira.update_issue_field.assert_called_with( + key="JBI-234", + fields={"components": [{"id": "42"}]}, + ) + def test_sync_whiteboard_labels( action_context_factory, mocked_jira,