Skip to content

Commit e5fab00

Browse files
committed
wip: release pipeline
1 parent 39cb60d commit e5fab00

14 files changed

Lines changed: 562 additions & 32 deletions

File tree

api/app/settings/common.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@
113113
"features.multivariate",
114114
"features.versioning",
115115
"features.workflows.core",
116+
"features.release_pipelines.core",
116117
"segments",
117118
"app",
118119
"e2etests",
@@ -1066,6 +1067,14 @@
10661067
if importlib.util.find_spec("workflows_logic.stale_flags") is not None:
10671068
INSTALLED_APPS.append("workflows_logic.stale_flags")
10681069

1070+
RELEASE_PIPELINES_LOGIC_INSTALLED = (
1071+
importlib.util.find_spec("release_pipelines_logic") is not None
1072+
)
1073+
1074+
if RELEASE_PIPELINES_LOGIC_INSTALLED:
1075+
INSTALLED_APPS.append("release_pipelines_logic")
1076+
1077+
10691078
# Additional functionality for restricting authentication to a set of authentication methods in Flagsmith SaaS
10701079
AUTH_CONTROLLER_INSTALLED = importlib.util.find_spec("auth_controller") is not None
10711080
if AUTH_CONTROLLER_INSTALLED:

api/conftest.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -548,7 +548,7 @@ def multivariate_options(
548548

549549

550550
@pytest.fixture()
551-
def identity_matching_segment(project, trait): # type: ignore[no-untyped-def]
551+
def identity_matching_segment(project: Project, trait: Trait) -> Segment:
552552
segment = Segment.objects.create(name="Matching segment", project=project)
553553
matching_rule = SegmentRule.objects.create(
554554
segment=segment, type=SegmentRule.ALL_RULE
@@ -567,6 +567,13 @@ def api_client(): # type: ignore[no-untyped-def]
567567
return APIClient()
568568

569569

570+
@pytest.fixture()
571+
def sdk_client(environment: Environment) -> APIClient:
572+
client = APIClient()
573+
client.credentials(HTTP_X_ENVIRONMENT_KEY=environment.api_key)
574+
return client
575+
576+
570577
@pytest.fixture()
571578
def feature(project: Project, environment: Environment) -> Feature:
572579
return Feature.objects.create(name="Test Feature1", project=project) # type: ignore[no-any-return]

api/features/release_pipelines/core/__init__.py

Whitespace-only changes.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from django.apps import AppConfig
2+
3+
4+
class ReleasePipelineConfig(AppConfig):
5+
name = "features.release_pipelines.core"
6+
label = "release_pipelines_core"
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from rest_framework import status
2+
from rest_framework.exceptions import APIException
3+
4+
5+
class ReleasePipelineError(APIException):
6+
status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
7+
8+
9+
class InvalidPipelineStateError(ReleasePipelineError):
10+
status_code = status.HTTP_400_BAD_REQUEST # type: ignore[assignment]
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
# Generated by Django 4.2.21 on 2025-06-09 03:49
2+
3+
from django.conf import settings
4+
import django.core.validators
5+
from django.db import migrations, models
6+
import django.db.models.deletion
7+
8+
9+
class Migration(migrations.Migration):
10+
11+
initial = True
12+
13+
dependencies = [
14+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
15+
("projects", "0027_add_create_project_level_change_requests_permission"),
16+
("environments", "0037_add_uuid_field"),
17+
]
18+
19+
operations = [
20+
migrations.CreateModel(
21+
name="PipelineStage",
22+
fields=[
23+
(
24+
"id",
25+
models.AutoField(
26+
auto_created=True,
27+
primary_key=True,
28+
serialize=False,
29+
verbose_name="ID",
30+
),
31+
),
32+
("name", models.CharField(max_length=255)),
33+
(
34+
"order",
35+
models.PositiveSmallIntegerField(
36+
validators=[django.core.validators.MaxValueValidator(30)]
37+
),
38+
),
39+
(
40+
"environment",
41+
models.ForeignKey(
42+
on_delete=django.db.models.deletion.CASCADE,
43+
related_name="pipeline_stages",
44+
to="environments.environment",
45+
),
46+
),
47+
],
48+
),
49+
migrations.CreateModel(
50+
name="ReleasePipeline",
51+
fields=[
52+
(
53+
"id",
54+
models.AutoField(
55+
auto_created=True,
56+
primary_key=True,
57+
serialize=False,
58+
verbose_name="ID",
59+
),
60+
),
61+
("name", models.CharField(max_length=255)),
62+
("description", models.TextField(blank=True, null=True)),
63+
("published_at", models.DateTimeField(blank=True, null=True)),
64+
(
65+
"project",
66+
models.ForeignKey(
67+
on_delete=django.db.models.deletion.CASCADE,
68+
related_name="release_pipelines",
69+
to="projects.project",
70+
),
71+
),
72+
(
73+
"published_by",
74+
models.ForeignKey(
75+
blank=True,
76+
null=True,
77+
on_delete=django.db.models.deletion.SET_NULL,
78+
related_name="published_release_pipelines",
79+
to=settings.AUTH_USER_MODEL,
80+
),
81+
),
82+
],
83+
),
84+
migrations.CreateModel(
85+
name="PipelineStageTrigger",
86+
fields=[
87+
(
88+
"id",
89+
models.AutoField(
90+
auto_created=True,
91+
primary_key=True,
92+
serialize=False,
93+
verbose_name="ID",
94+
),
95+
),
96+
(
97+
"trigger_type",
98+
models.CharField(
99+
choices=[
100+
("ON_ENTER", "Trigger when flag enters stage"),
101+
("WAIT_FOR", "Trigger after waiting for x amount of time"),
102+
],
103+
default="ON_ENTER",
104+
max_length=50,
105+
),
106+
),
107+
("trigger_body", models.JSONField(null=True)),
108+
(
109+
"stage",
110+
models.OneToOneField(
111+
on_delete=django.db.models.deletion.CASCADE,
112+
related_name="trigger",
113+
to="release_pipelines_core.pipelinestage",
114+
),
115+
),
116+
],
117+
),
118+
migrations.CreateModel(
119+
name="PipelineStageAction",
120+
fields=[
121+
(
122+
"id",
123+
models.AutoField(
124+
auto_created=True,
125+
primary_key=True,
126+
serialize=False,
127+
verbose_name="ID",
128+
),
129+
),
130+
(
131+
"action_type",
132+
models.CharField(
133+
choices=[
134+
(
135+
"TOGGLE_FEATURE",
136+
"Enable/Disable Feature for the environment",
137+
),
138+
(
139+
"UPDATE_FEATURE_VALUE",
140+
"Update Feature Value for the environment",
141+
),
142+
(
143+
"TOGGLE_FEATURE_FOR_SEGMENT",
144+
"Enable/Disable Feature for a specific segment",
145+
),
146+
(
147+
"UPDATE_FEATURE_VALUE_FOR_SEGMENT",
148+
"Update Feature Value for a specific segment",
149+
),
150+
],
151+
default="TOGGLE_FEATURE",
152+
max_length=50,
153+
),
154+
),
155+
("action_body", models.JSONField(null=True)),
156+
(
157+
"stage",
158+
models.ForeignKey(
159+
on_delete=django.db.models.deletion.CASCADE,
160+
related_name="actions",
161+
to="release_pipelines_core.pipelinestage",
162+
),
163+
),
164+
],
165+
),
166+
migrations.AddField(
167+
model_name="pipelinestage",
168+
name="pipeline",
169+
field=models.ForeignKey(
170+
on_delete=django.db.models.deletion.CASCADE,
171+
related_name="stages",
172+
to="release_pipelines_core.releasepipeline",
173+
),
174+
),
175+
migrations.AddConstraint(
176+
model_name="pipelinestage",
177+
constraint=models.UniqueConstraint(
178+
fields=("pipeline", "order"), name="unique_pipeline_stage_order"
179+
),
180+
),
181+
]

api/features/release_pipelines/core/migrations/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)