Skip to content

Commit

Permalink
feat : HEXA-1124 button to create a new pipeline from a template (#887)
Browse files Browse the repository at this point in the history
  • Loading branch information
YolanFery authored Dec 24, 2024
1 parent 6e518f6 commit fee5a93
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 1 deletion.
28 changes: 28 additions & 0 deletions hexa/pipeline_templates/graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ extend type Mutation {
Creates a new pipeline template version.
"""
createPipelineTemplateVersion(input: CreatePipelineTemplateVersionInput!): CreatePipelineTemplateVersionResult! @loginRequired
createPipelineFromTemplateVersion(input: CreatePipelineFromTemplateVersionInput!): CreatePipelineFromTemplateVersionResult! @loginRequired
}

"""
Expand All @@ -27,6 +28,33 @@ type CreatePipelineTemplateVersionResult {
errors: [CreatePipelineTemplateVersionError!] # The list of errors that occurred during the creation of the pipeline template version.
}

"""
Represents the input for creating a new pipeline from a template version.
"""
input CreatePipelineFromTemplateVersionInput {
workspaceSlug: String! # The slug of the pipeline workspace.
pipelineTemplateVersionId: UUID! # The ID of the pipeline template version.
}

"""
Represents the result of creating a new pipeline from a template version.
"""
type CreatePipelineFromTemplateVersionResult {
pipeline: Pipeline # The created pipeline.
success: Boolean! # Indicates if the pipeline was created successfully.
errors: [CreatePipelineFromTemplateVersionError!] # The list of errors that occurred during the creation of the pipeline.
}

"""
Enum representing the possible errors that can occur when creating a pipeline from a template version.
"""
enum CreatePipelineFromTemplateVersionError {
PERMISSION_DENIED
WORKSPACE_NOT_FOUND
PIPELINE_TEMPLATE_VERSION_NOT_FOUND
PIPELINE_ALREADY_EXISTS
}

"""
Enum representing the possible errors that can occur when creating a pipeline template version.
"""
Expand Down
21 changes: 21 additions & 0 deletions hexa/pipeline_templates/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,5 +95,26 @@ class Meta:

objects = PipelineTemplateVersionQuerySet.as_manager()

def create_pipeline(self, code, workspace, user):
source_pipeline = self.template.source_pipeline
source_version = self.source_pipeline_version
pipeline = Pipeline.objects.create(
source_template=self.template,
code=code,
name=source_pipeline.name,
description=source_pipeline.description,
config=source_pipeline.config,
workspace=workspace,
)
PipelineVersion.objects.create(
user=user,
pipeline=pipeline,
zipfile=source_version.zipfile,
parameters=source_version.parameters,
config=source_version.config,
timeout=source_version.timeout,
)
return pipeline

def __str__(self):
return f"v{self.version_number} of {self.template.name}"
37 changes: 37 additions & 0 deletions hexa/pipeline_templates/schema/mutations.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from django.http import HttpRequest

from hexa.analytics.api import track
from hexa.pipeline_templates.models import PipelineTemplateVersion
from hexa.pipelines.models import Pipeline, PipelineVersion
from hexa.workspaces.models import Workspace

Expand Down Expand Up @@ -74,4 +75,40 @@ def resolve_create_pipeline_template_version(_, info, **kwargs):
return {"pipeline_template": pipeline_template, "success": True, "errors": []}


@pipeline_template_mutations.field("createPipelineFromTemplateVersion")
def resolve_create_pipeline_from_template_version(_, info, **kwargs):
request: HttpRequest = info.context["request"]
input = kwargs["input"]

workspace = get_workspace(request.user, input.get("workspace_slug"))
if not workspace:
return {"success": False, "errors": ["WORKSPACE_NOT_FOUND"]}

if not request.user.has_perm("pipelines.create_pipeline", workspace):
return {"success": False, "errors": ["PERMISSION_DENIED"]}

try:
template_version = PipelineTemplateVersion.objects.get(
id=input["pipeline_template_version_id"]
)
except PipelineTemplateVersion.DoesNotExist:
return {"success": False, "errors": ["PIPELINE_TEMPLATE_VERSION_NOT_FOUND"]}

pipeline_code = f"{template_version.template.source_pipeline.code} (from Template)"
if Pipeline.objects.filter(workspace=workspace, code=pipeline_code).exists():
return {"success": False, "errors": ["PIPELINE_ALREADY_EXISTS"]}
pipeline = template_version.create_pipeline(pipeline_code, workspace, request.user)

track(
request,
"pipeline_templates.pipeline_created_from_template",
{
"pipeline_id": str(pipeline.id),
"template_version_id": str(template_version.id),
"workspace": workspace.slug,
},
)
return {"pipeline": pipeline, "success": True, "errors": []}


bindables = [pipeline_template_mutations]
70 changes: 69 additions & 1 deletion hexa/pipeline_templates/tests/test_schema/test_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,16 @@ def setUpTestData(cls):
name="WS1",
description="Workspace 1",
)
cls.PIPELINE = Pipeline.objects.create(name="Test Pipeline", workspace=cls.WS1)
cls.PIPELINE = Pipeline.objects.create(
name="Test Pipeline", code="Test Pipeline", workspace=cls.WS1
)
cls.PIPELINE_VERSION1 = PipelineVersion.objects.create(
pipeline=cls.PIPELINE,
version_number=1,
description="Initial version",
parameters=[{"code": "param_1"}],
config=[{"param_1": 1}],
zipfile=str.encode("some_bytes"),
)
cls.PIPELINE_VERSION2 = PipelineVersion.objects.create(
pipeline=cls.PIPELINE,
Expand Down Expand Up @@ -84,3 +89,66 @@ def test_create_template_version(self):
self.create_template_version(
self.PIPELINE_VERSION2.id, [{"versionNumber": 1}, {"versionNumber": 2}]
)

def test_create_pipeline_from_template_version(self):
self.client.force_login(self.USER_ROOT)
self.create_template_version(self.PIPELINE_VERSION1.id, [{"versionNumber": 1}])
r = self.run_query(
"""
mutation createPipelineFromTemplateVersion($input: CreatePipelineFromTemplateVersionInput!) {
createPipelineFromTemplateVersion(input: $input) {
success errors pipeline {name code currentVersion {zipfile parameters {code default} config}}
}
}
""",
{
"input": {
"workspaceSlug": self.WS1.slug,
"pipelineTemplateVersionId": str(
self.PIPELINE_VERSION1.template_version.id
),
}
},
)
self.assertEqual(
{
"success": True,
"errors": [],
"pipeline": {
"name": self.PIPELINE.name,
"code": "Test Pipeline (from Template)",
"currentVersion": {
"zipfile": "c29tZV9ieXRlcw==",
"parameters": [{"code": "param_1", "default": None}],
"config": [{"param_1": 1}],
},
},
},
r["data"]["createPipelineFromTemplateVersion"],
)

r = self.run_query(
"""
mutation createPipelineFromTemplateVersion($input: CreatePipelineFromTemplateVersionInput!) {
createPipelineFromTemplateVersion(input: $input) {
success errors pipeline {name code currentVersion {zipfile parameters {code default} config}}
}
}
""",
{
"input": {
"workspaceSlug": self.WS1.slug,
"pipelineTemplateVersionId": str(
self.PIPELINE_VERSION1.template_version.id
),
}
},
)
self.assertEqual(
{
"success": False,
"errors": ["PIPELINE_ALREADY_EXISTS"],
"pipeline": None,
},
r["data"]["createPipelineFromTemplateVersion"],
)
25 changes: 25 additions & 0 deletions hexa/pipelines/migrations/0054_pipeline_source_template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Generated by Django 4.2.17 on 2024-12-20 16:26

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("pipeline_templates", "0002_alter_pipelinetemplate_name"),
("pipelines", "0053_pipelinerun_log_level"),
]

operations = [
migrations.AddField(
model_name="pipeline",
name="source_template",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="pipelines",
to="pipeline_templates.pipelinetemplate",
),
),
]
7 changes: 7 additions & 0 deletions hexa/pipelines/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,13 @@ class Meta:
default=PipelineType.ZIPFILE,
)
notebook_path = models.TextField(null=True, blank=True)
source_template = models.ForeignKey(
"pipeline_templates.PipelineTemplate",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="pipelines",
)

objects = DefaultSoftDeletedManager.from_queryset(PipelineQuerySet)()
all_objects = IncludeSoftDeletedManager.from_queryset(PipelineQuerySet)()
Expand Down

0 comments on commit fee5a93

Please sign in to comment.