Skip to content

Commit 666dce2

Browse files
dklawrendependabot[bot]alexcottner
authored
feature: Add step to allow auto creating of components that do not yet exist in Jira for a project (#1226)
* feature: Add step to allow auto creating of components that do not yet exist in Jira for a project * Update pytest requirement from <9.0.0,>=8.4.2 to >=8.4.2,<10.0.0 (#1222) * Update pytest requirement from <9.0.0,>=8.4.2 to >=8.4.2,<10.0.0 Updates the requirements on [pytest](https://github.com/pytest-dev/pytest) to permit the latest version. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](pytest-dev/pytest@8.4.2...9.0.0) --- updated-dependencies: - dependency-name: pytest dependency-version: 9.0.0 dependency-type: direct:development ... Signed-off-by: dependabot[bot] <[email protected]> * ran uv lock * Switched package ecosystem to uv --------- Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Alex Cottner <[email protected]> * Bump pytest from 8.4.2 to 9.0.1 (#1224) Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.4.2 to 9.0.1. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](pytest-dev/pytest@8.4.2...9.0.1) --- updated-dependencies: - dependency-name: pytest dependency-version: 9.0.1 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump the minor-patch group across 1 directory with 5 updates (#1225) Bumps the minor-patch group with 5 updates in the / directory: | Package | From | To | | --- | --- | --- | | [fastapi](https://github.com/fastapi/fastapi) | `0.120.2` | `0.121.1` | | [pydantic-settings](https://github.com/pydantic/pydantic-settings) | `2.11.0` | `2.12.0` | | [pypandoc](https://github.com/JessicaTegner/pypandoc) | `1.15` | `1.16` | | [pre-commit](https://github.com/pre-commit/pre-commit) | `4.3.0` | `4.4.0` | | [ruff](https://github.com/astral-sh/ruff) | `0.14.2` | `0.14.4` | Updates `fastapi` from 0.120.2 to 0.121.1 - [Release notes](https://github.com/fastapi/fastapi/releases) - [Commits](fastapi/fastapi@0.120.2...0.121.1) Updates `pydantic-settings` from 2.11.0 to 2.12.0 - [Release notes](https://github.com/pydantic/pydantic-settings/releases) - [Commits](pydantic/pydantic-settings@v2.11.0...v2.12.0) Updates `pypandoc` from 1.15 to 1.16 - [Release notes](https://github.com/JessicaTegner/pypandoc/releases) - [Changelog](https://github.com/JessicaTegner/pypandoc/blob/master/release.md) - [Commits](JessicaTegner/pypandoc@v1.15...v1.16) Updates `pre-commit` from 4.3.0 to 4.4.0 - [Release notes](https://github.com/pre-commit/pre-commit/releases) - [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md) - [Commits](pre-commit/pre-commit@v4.3.0...v4.4.0) Updates `ruff` from 0.14.2 to 0.14.4 - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](astral-sh/ruff@0.14.2...0.14.4) --- updated-dependencies: - dependency-name: fastapi dependency-version: 0.121.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: pydantic-settings dependency-version: 2.12.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: pypandoc dependency-version: '1.16' dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: pre-commit dependency-version: 4.4.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: ruff dependency-version: 0.14.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-patch ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Add missing parameter for JiraComponents * make format * - Return early is not create_components is False - Added test cases for creating components in Jira if they do not exist --------- Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Alex Cottner <[email protected]>
1 parent 78330cc commit 666dce2

File tree

6 files changed

+96
-5
lines changed

6 files changed

+96
-5
lines changed

config/config.prod.yaml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -844,8 +844,9 @@
844844
parameters:
845845
jira_project_key: BZFFX
846846
jira_components:
847-
set_custom_components:
848-
- "Firefox"
847+
use_bug_component_with_product_prefix: true
848+
use_bug_component: false
849+
create_components: true
849850
steps:
850851
new:
851852
- create_issue
@@ -885,8 +886,9 @@
885886
parameters:
886887
jira_project_key: BZFFX
887888
jira_components:
888-
set_custom_components:
889-
- "Core"
889+
use_bug_component_with_product_prefix: true
890+
use_bug_component: false
891+
create_components: true
890892
steps:
891893
new:
892894
- create_issue

jbi/jira/client.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ def raise_for_status(self, *args, **kwargs):
7070

7171
get_server_info = instrumented_method(Jira.get_server_info)
7272
get_project_components = instrumented_method(Jira.get_project_components)
73+
create_component = instrumented_method(Jira.create_component)
7374
update_issue = instrumented_method(Jira.update_issue)
7475
update_issue_field = instrumented_method(Jira.update_issue_field)
7576
set_issue_status = instrumented_method(Jira.set_issue_status)

jbi/jira/service.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,7 @@ def update_issue_components(
350350
self,
351351
context: ActionContext,
352352
components: Iterable[str],
353+
create_components: bool = False,
353354
) -> tuple[Optional[dict], set]:
354355
"""Attempt to add components to the specified issue
355356
@@ -374,7 +375,16 @@ def update_issue_components(
374375
missing_components.remove(comp["name"])
375376

376377
if not jira_components:
377-
return None, missing_components
378+
if not create_components:
379+
return None, missing_components
380+
381+
for comp_name in missing_components:
382+
new_comp = self.client.create_component(
383+
project_key=context.jira.project,
384+
name=comp_name,
385+
)
386+
jira_components.append({"id": new_comp["id"]})
387+
missing_components.clear()
378388

379389
resp = self.update_issue_field(
380390
context, field="components", value=jira_components

jbi/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ class JiraComponents(BaseModel, frozen=True):
8080
use_bug_component: bool = True
8181
use_bug_product: bool = False
8282
use_bug_component_with_product_prefix: bool = False
83+
create_components: bool = False
8384
set_custom_components: list[str] = []
8485

8586

jbi/steps.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,7 @@ def maybe_update_components(
405405
resp, missing_components = jira_service.update_issue_components(
406406
context=context,
407407
components=candidate_components,
408+
create_components=parameters.jira_components.create_components,
408409
)
409410
except requests_exceptions.HTTPError as exc:
410411
if getattr(exc.response, "status_code", None) != 400:

tests/unit/test_steps.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1351,6 +1351,82 @@ def test_maybe_update_components_failing(
13511351
]
13521352

13531353

1354+
def test_maybe_update_components_create_components_normal_component(
1355+
action_context_factory,
1356+
mocked_jira,
1357+
action_params_factory,
1358+
):
1359+
action_context = action_context_factory(
1360+
operation=Operation.CREATE,
1361+
current_step="maybe_update_components",
1362+
bug__component="NewComponent",
1363+
jira__issue="JBI-234",
1364+
)
1365+
action_params = action_params_factory(
1366+
jira_project_key=action_context.jira.project,
1367+
jira_components=JiraComponents(create_components=True),
1368+
)
1369+
mocked_jira.get_project_components.return_value = [
1370+
{"id": 1, "name": "ExistingComponent"},
1371+
]
1372+
mocked_jira.create_component.return_value = {"id": "42", "name": "NewComponent"}
1373+
1374+
steps.maybe_update_components(
1375+
action_context,
1376+
parameters=action_params,
1377+
jira_service=JiraService(mocked_jira),
1378+
)
1379+
1380+
mocked_jira.create_component.assert_called_once_with(
1381+
project_key=action_context.jira.project,
1382+
name="NewComponent",
1383+
)
1384+
mocked_jira.update_issue_field.assert_called_with(
1385+
key="JBI-234",
1386+
fields={"components": [{"id": "42"}]},
1387+
)
1388+
1389+
1390+
def test_maybe_update_components_create_components_prefix_component(
1391+
action_context_factory,
1392+
mocked_jira,
1393+
action_params_factory,
1394+
):
1395+
action_context = action_context_factory(
1396+
operation=Operation.CREATE,
1397+
current_step="maybe_update_components",
1398+
bug__product="Firefox",
1399+
bug__component="NewComponent",
1400+
jira__issue="JBI-234",
1401+
)
1402+
action_params = action_params_factory(
1403+
jira_project_key=action_context.jira.project,
1404+
jira_components=JiraComponents(
1405+
create_components=True,
1406+
use_bug_component_with_product_prefix=True,
1407+
use_bug_component=False
1408+
),
1409+
)
1410+
mocked_jira.get_project_components.return_value = [
1411+
{"id": 1, "name": "Firefox::ExistingComponent"},
1412+
]
1413+
mocked_jira.create_component.return_value = {"id": "42", "name": "Firefox::NewComponent"}
1414+
1415+
steps.maybe_update_components(
1416+
action_context,
1417+
parameters=action_params,
1418+
jira_service=JiraService(mocked_jira),
1419+
)
1420+
1421+
mocked_jira.create_component.assert_called_once_with(
1422+
project_key=action_context.jira.project,
1423+
name="Firefox::NewComponent",
1424+
)
1425+
mocked_jira.update_issue_field.assert_called_with(
1426+
key="JBI-234",
1427+
fields={"components": [{"id": "42"}]},
1428+
)
1429+
13541430
def test_sync_whiteboard_labels(
13551431
action_context_factory,
13561432
mocked_jira,

0 commit comments

Comments
 (0)