Skip to content

Commit 45a61be

Browse files
authored
feat: Support template version creation on push (HEXA-1182) (#226)
1 parent c1b703c commit 45a61be

3 files changed

Lines changed: 152 additions & 7 deletions

File tree

openhexa/cli/api.py

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,17 @@ def upload_pipeline(
562562
pipelineVersion {
563563
id
564564
versionName
565+
pipeline {
566+
id
567+
permissions {
568+
createTemplateVersion
569+
}
570+
template {
571+
id
572+
code
573+
name
574+
}
575+
}
565576
}
566577
}
567578
}
@@ -598,6 +609,73 @@ def upload_pipeline(
598609
return data["uploadPipeline"]["pipelineVersion"]
599610

600611

612+
class PipelineTemplateVersionCreateErrorCode(enum.Enum):
613+
"""Enumeration of possible pipeline template version create error codes."""
614+
615+
PERMISSION_DENIED = "PERMISSION_DENIED"
616+
WORKSPACE_NOT_FOUND = "WORKSPACE_NOT_FOUND"
617+
PIPELINE_NOT_FOUND = "PIPELINE_NOT_FOUND"
618+
PIPELINE_VERSION_NOT_FOUND = "PIPELINE_VERSION_NOT_FOUND"
619+
620+
621+
def create_pipeline_template_version(
622+
workspace_slug: str, pipeline_id: str, pipeline_version_id: str, changelog: str = None
623+
):
624+
"""Create a pipeline template version using the API."""
625+
data = graphql(
626+
"""
627+
mutation createPipelineTemplateVersion($input: CreatePipelineTemplateVersionInput!) {
628+
createPipelineTemplateVersion(input: $input) {
629+
success
630+
errors
631+
pipelineTemplate {
632+
id
633+
name
634+
code
635+
currentVersion {
636+
id
637+
versionNumber
638+
}
639+
}
640+
}
641+
}
642+
""",
643+
{
644+
"input": {
645+
"workspaceSlug": workspace_slug,
646+
"pipelineId": pipeline_id,
647+
"pipelineVersionId": pipeline_version_id,
648+
"changelog": changelog,
649+
}
650+
},
651+
)
652+
if not data["createPipelineTemplateVersion"]["success"]:
653+
if (
654+
PipelineTemplateVersionCreateErrorCode.PERMISSION_DENIED.value
655+
in data["createPipelineTemplateVersion"]["errors"]
656+
):
657+
raise PermissionDenied("Permission denied")
658+
elif (
659+
PipelineTemplateVersionCreateErrorCode.WORKSPACE_NOT_FOUND.value
660+
in data["createPipelineTemplateVersion"]["errors"]
661+
):
662+
raise Exception("Workspace not found")
663+
elif (
664+
PipelineTemplateVersionCreateErrorCode.PIPELINE_NOT_FOUND.value
665+
in data["createPipelineTemplateVersion"]["errors"]
666+
):
667+
raise Exception("Pipeline not found")
668+
elif (
669+
PipelineTemplateVersionCreateErrorCode.PIPELINE_VERSION_NOT_FOUND.value
670+
in data["createPipelineTemplateVersion"]["errors"]
671+
):
672+
raise Exception("Pipeline version not found for this pipeline")
673+
else:
674+
raise Exception(data["createPipelineTemplateVersion"]["errors"])
675+
676+
return data["createPipelineTemplateVersion"]["pipelineTemplate"]
677+
678+
601679
def is_dhis2_connection_up(workspace_slug: str, connection_slug: str) -> bool:
602680
"""DHIS2 connection status."""
603681
response = graphql(
@@ -615,4 +693,4 @@ def is_dhis2_connection_up(workspace_slug: str, connection_slug: str) -> bool:
615693
"connectionSlug": connection_slug,
616694
},
617695
)
618-
response["data"]["connectionBySlug"]["status"] == "UP"
696+
return response["data"]["connectionBySlug"]["status"] == "UP"

openhexa/cli/cli.py

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import json
44
import signal
5+
import urllib
56
from datetime import datetime
67
from importlib.metadata import version
78
from pathlib import Path
@@ -17,6 +18,7 @@
1718
PipelineDirectoryError,
1819
create_pipeline,
1920
create_pipeline_structure,
21+
create_pipeline_template_version,
2022
delete_pipeline,
2123
download_pipeline_sourcecode,
2224
ensure_is_pipeline_dir,
@@ -224,6 +226,40 @@ def pipelines_init(name: str):
224226
)
225227

226228

229+
def propose_to_create_template_version(workspace, pipeline_version, yes):
230+
"""Propose to create a new version of the template if the pipeline is based on a template."""
231+
pipeline = pipeline_version["pipeline"]
232+
if pipeline["template"] and pipeline["permissions"]["createTemplateVersion"]:
233+
if not yes:
234+
click.confirm(
235+
f"The template {click.style(pipeline['template']['name'], bold=True)} is based on this pipeline, do you want to publish a new version of the template as well?",
236+
True,
237+
abort=True,
238+
)
239+
changelog = (
240+
""
241+
if yes
242+
else click.prompt(
243+
f"{click.style('Changelog', bold=True)} (optional)", type=str, default="", show_default=False
244+
)
245+
)
246+
try:
247+
template = create_pipeline_template_version(workspace, pipeline["id"], pipeline_version["id"], changelog)
248+
template_code_url = urllib.parse.quote(template["code"])
249+
click.echo(
250+
click.style(
251+
f"✅ New version '{template['currentVersion']['versionNumber']}' of the template '{template['name']}' created! You can view the new template version in OpenHEXA on {click.style(f'{settings.public_api_url}/workspaces/{workspace}/templates/{template_code_url}/versions', fg='bright_blue', underline=True)}",
252+
fg="green",
253+
)
254+
)
255+
except Exception as e:
256+
_terminate(
257+
f'❌ Error while creating template version: "{e}"',
258+
err=True,
259+
exception=e,
260+
)
261+
262+
227263
@pipelines.command("push")
228264
@click.argument(
229265
"path",
@@ -317,11 +353,12 @@ def pipelines_push(
317353
)
318354
click.confirm(confirmation_message, default=True, abort=True)
319355

356+
uploaded_pipeline_version = None
320357
try:
321-
uploaded_pipeline = upload_pipeline(path, name, description=description, link=link)
358+
uploaded_pipeline_version = upload_pipeline(path, name, description=description, link=link)
322359
click.echo(
323360
click.style(
324-
f"✅ New version '{uploaded_pipeline['versionName']}' created! You can view the pipeline in OpenHEXA on {click.style(f'{settings.public_api_url}/workspaces/{workspace}/pipelines/{pipeline.code}', fg='bright_blue', underline=True)}",
361+
f"✅ New version '{uploaded_pipeline_version['versionName']}' created! You can view the pipeline in OpenHEXA on {click.style(f'{settings.public_api_url}/workspaces/{workspace}/pipelines/{pipeline.code}', fg='bright_blue', underline=True)}",
325362
fg="green",
326363
)
327364
)
@@ -337,6 +374,7 @@ def pipelines_push(
337374
err=True,
338375
exception=e,
339376
)
377+
propose_to_create_template_version(workspace, uploaded_pipeline_version, yes)
340378

341379

342380
@pipelines.command("download")

tests/test_cli.py

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@
1919
python_code = "print('pipeline.py file')"
2020
version = "v1.0"
2121
pipeline_name = "MyPipeline"
22+
pipeline_id = "pipeline_id"
23+
pipeline_version_id = "pipeline_version_id"
24+
template = {
25+
"id": "template_id",
26+
"name": "test_template",
27+
"code": "template_code",
28+
"currentVersion": {"versionNumber": "1.0"},
29+
}
30+
changelog = "Changelog for the new version"
2231

2332

2433
def create_zip_with_pipeline():
@@ -116,8 +125,9 @@ def test_download_pipeline(self, mock_graphql):
116125
@patch("openhexa.cli.api.graphql")
117126
@patch("openhexa.cli.cli.get_pipeline")
118127
@patch("openhexa.cli.cli.upload_pipeline")
128+
@patch("openhexa.cli.cli.create_pipeline_template_version")
119129
@patch.dict(os.environ, {"HEXA_API_URL": "https://www.bluesquarehub.com/", "HEXA_WORKSPACE": "workspace"})
120-
def test_push_pipeline(self, mock_upload_pipeline, mock_get_pipeline, mock_graphql):
130+
def test_push_pipeline(self, mock_create_template, mock_upload_pipeline, mock_get_pipeline, mock_graphql):
121131
"""Test pushing a pipeline."""
122132
with self.runner.isolated_filesystem() as tmp:
123133
with open(Path(tmp) / python_file_name, "w") as f:
@@ -126,9 +136,20 @@ def test_push_pipeline(self, mock_upload_pipeline, mock_get_pipeline, mock_graph
126136
mock_pipeline = MagicMock(spec=Pipeline)
127137
mock_pipeline.code = pipeline_name
128138
mock_get_pipeline.return_value = mock_pipeline
129-
mock_upload_pipeline.return_value = {"versionName": version}
130-
131-
result = self.runner.invoke(pipelines_push, [tmp, "--name", version])
139+
mock_upload_pipeline.return_value = {
140+
"versionName": version,
141+
"pipeline": {
142+
"id": pipeline_id,
143+
"permissions": {"createTemplateVersion": True},
144+
"template": template,
145+
},
146+
"id": pipeline_version_id,
147+
}
148+
mock_create_template.return_value = template
149+
150+
result = self.runner.invoke(
151+
pipelines_push, [tmp, "--name", version], input="\n".join(["Y", "Y", changelog]) + "\n"
152+
)
132153
self.assertEqual(result.exit_code, 0)
133154
self.assertIn(
134155
(
@@ -138,6 +159,14 @@ def test_push_pipeline(self, mock_upload_pipeline, mock_get_pipeline, mock_graph
138159
result.output,
139160
)
140161
self.assertTrue(mock_upload_pipeline.called)
162+
mock_create_template.assert_called_with("workspace", pipeline_id, pipeline_version_id, changelog)
163+
self.assertIn(
164+
(
165+
f"✅ New version '{template['currentVersion']['versionNumber']}' of the template '{template['name']}' created! "
166+
f"You can view the new template version in OpenHEXA on https://www.bluesquarehub.com/workspaces/workspace/templates/{template['code']}/versions"
167+
),
168+
result.output,
169+
)
141170

142171
@patch("openhexa.cli.api.graphql")
143172
def test_workspaces_add_not_found(self, mock_graphql):

0 commit comments

Comments
 (0)