Skip to content

Commit 1f3bbd7

Browse files
authored
Merge pull request #1290 from BudEcosystem/fix/1260-pipeline-icon-persistence
fix(budpipeline): persist pipeline icon across full stack
2 parents a8ca080 + 1fa8ef7 commit 1f3bbd7

File tree

12 files changed

+100
-17
lines changed

12 files changed

+100
-17
lines changed

services/budadmin/src/flows/Pipeline/EditPipeline.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export default function EditPipeline() {
2828
<BudForm
2929
data={{
3030
name: pipeline.name || "",
31+
icon: pipeline.icon || "🔄",
3132
description: pipeline.dag?.description || "",
3233
}}
3334
drawerLoading={isSaving}
@@ -50,7 +51,7 @@ export default function EditPipeline() {
5051
description: values.description,
5152
};
5253

53-
const result = await updateWorkflow(pipeline.id, updatedDag);
54+
const result = await updateWorkflow(pipeline.id, updatedDag, values.icon);
5455
if (result) {
5556
successToast("Pipeline updated successfully");
5657
closeDrawer();

services/budadmin/src/flows/Pipeline/NewPipeline.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export default function NewPipeline() {
4848
outputs: {},
4949
};
5050

51-
const result = await createWorkflow(dag);
51+
const result = await createWorkflow(dag, values.icon);
5252
if (result) {
5353
successToast("Pipeline draft created");
5454
closeDrawer();

services/budadmin/src/pages/home/budpipelines/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ const WorkflowCard = ({
6161
{/* Header with icon */}
6262
<div className="pr-0 flex justify-between items-start gap-3">
6363
<div className="w-[2.40125rem] h-[2.40125rem] bg-[#1F1F1F] rounded-[5px] flex items-center justify-center text-xl">
64-
🔄
64+
{workflow.icon || "🔄"}
6565
</div>
6666
<ConfigProvider
6767
theme={{

services/budadmin/src/stores/useBudPipeline.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export type BudPipelineItem = {
7272
updated_at?: string;
7373
step_count: number;
7474
dag: DAGDefinition;
75+
icon?: string;
7576
warnings?: string[];
7677
execution_count?: number;
7778
last_execution_at?: string;
@@ -645,8 +646,8 @@ type BudPipelineStore = {
645646
getWorkflow: (id: string) => Promise<void>;
646647
getExecutions: (workflowId?: string, page?: number, pageSize?: number) => Promise<void>;
647648
getExecution: (executionId: string) => Promise<void>;
648-
createWorkflow: (dag: DAGDefinition) => Promise<BudPipelineItem | null>;
649-
updateWorkflow: (id: string, dag: DAGDefinition) => Promise<BudPipelineItem | null>;
649+
createWorkflow: (dag: DAGDefinition, icon?: string) => Promise<BudPipelineItem | null>;
650+
updateWorkflow: (id: string, dag: DAGDefinition, icon?: string) => Promise<BudPipelineItem | null>;
650651
executeWorkflow: (workflowId: string, params: Record<string, any>) => Promise<PipelineExecution | null>;
651652
deleteWorkflow: (id: string) => Promise<boolean>;
652653
validatePipeline: (dag: DAGDefinition) => Promise<ValidationResult>;
@@ -852,7 +853,7 @@ export const useBudPipeline = create<BudPipelineStore>((set, get) => ({
852853
},
853854

854855
// Create workflow
855-
createWorkflow: async (dag: DAGDefinition) => {
856+
createWorkflow: async (dag: DAGDefinition, icon?: string) => {
856857
set({ isLoading: true, error: null });
857858

858859
if (USE_MOCK_DATA) {
@@ -865,6 +866,7 @@ export const useBudPipeline = create<BudPipelineStore>((set, get) => ({
865866
created_at: new Date().toISOString(),
866867
step_count: dag.steps.length,
867868
dag,
869+
icon,
868870
execution_count: 0,
869871
};
870872
set((state) => ({
@@ -878,6 +880,7 @@ export const useBudPipeline = create<BudPipelineStore>((set, get) => ({
878880
const response = await AppRequest.Post(BUDPIPELINE_API, {
879881
dag,
880882
name: dag.name,
883+
icon,
881884
});
882885
const newWorkflow = response.data;
883886
set((state) => ({
@@ -896,7 +899,7 @@ export const useBudPipeline = create<BudPipelineStore>((set, get) => ({
896899
},
897900

898901
// Update workflow
899-
updateWorkflow: async (id: string, dag: DAGDefinition) => {
902+
updateWorkflow: async (id: string, dag: DAGDefinition, icon?: string) => {
900903
set({ isLoading: true, error: null });
901904

902905
if (USE_MOCK_DATA) {
@@ -910,6 +913,7 @@ export const useBudPipeline = create<BudPipelineStore>((set, get) => ({
910913
updated_at: new Date().toISOString(),
911914
step_count: dag.steps.length,
912915
dag,
916+
icon,
913917
execution_count: get().workflows.find((w) => w.id === id)?.execution_count || 0,
914918
};
915919
set((state) => ({
@@ -924,6 +928,7 @@ export const useBudPipeline = create<BudPipelineStore>((set, get) => ({
924928
const response = await AppRequest.Put(`${BUDPIPELINE_API}/${id}`, {
925929
dag,
926930
name: dag.name,
931+
icon,
927932
});
928933
const updatedWorkflow = response.data;
929934
set((state) => ({

services/budapp/budapp/workflow_ops/budpipeline_routes.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ async def create_budpipeline(
171171
name=request_body.get("name"),
172172
user_id=str(current_user.id),
173173
system_owned=request_body.get("system_owned", False),
174+
icon=request_body.get("icon"),
174175
)
175176
return JSONResponse(content=result, status_code=status.HTTP_201_CREATED)
176177
except ClientException as e:
@@ -1053,6 +1054,7 @@ async def update_budpipeline(
10531054
dag=request_body.get("dag"),
10541055
name=request_body.get("name"),
10551056
user_id=str(current_user.id),
1057+
icon=request_body.get("icon"),
10561058
)
10571059
return JSONResponse(content=result, status_code=status.HTTP_200_OK)
10581060
except ClientException as e:

services/budapp/budapp/workflow_ops/budpipeline_service.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ async def create_pipeline(
7373
name: Optional[str] = None,
7474
user_id: Optional[str] = None,
7575
system_owned: bool = False,
76+
icon: Optional[str] = None,
7677
) -> Dict[str, Any]:
7778
"""Create a new pipeline in budpipeline service.
7879
@@ -81,6 +82,7 @@ async def create_pipeline(
8182
name: Optional pipeline name override
8283
user_id: The ID of the user creating the pipeline
8384
system_owned: True if this is a system-owned pipeline visible to all users
85+
icon: Optional icon/emoji for UI representation
8486
8587
Returns:
8688
Created pipeline data including ID
@@ -89,16 +91,20 @@ async def create_pipeline(
8991
ClientException: If creation fails
9092
"""
9193
try:
94+
data: Dict[str, Any] = {
95+
"dag": dag,
96+
"name": name,
97+
"user_id": user_id,
98+
"system_owned": system_owned,
99+
}
100+
if icon is not None:
101+
data["icon"] = icon
102+
92103
result = await DaprService.invoke_service(
93104
app_id=BUDPIPELINE_APP_ID,
94105
method_path="pipelines",
95106
method="POST",
96-
data={
97-
"dag": dag,
98-
"name": name,
99-
"user_id": user_id,
100-
"system_owned": system_owned,
101-
},
107+
data=data,
102108
)
103109
return result
104110
except ClientException:
@@ -212,6 +218,7 @@ async def update_pipeline(
212218
dag: Optional[Dict[str, Any]] = None,
213219
name: Optional[str] = None,
214220
user_id: Optional[str] = None,
221+
icon: Optional[str] = None,
215222
) -> Dict[str, Any]:
216223
"""Update a pipeline's DAG definition.
217224
@@ -220,6 +227,7 @@ async def update_pipeline(
220227
dag: New DAG definition
221228
name: Optional new name
222229
user_id: User ID for permission check (optional)
230+
icon: Optional icon/emoji for UI representation
223231
224232
Returns:
225233
Updated pipeline data
@@ -228,11 +236,13 @@ async def update_pipeline(
228236
ClientException: If update fails
229237
"""
230238
try:
231-
data = {}
239+
data: Dict[str, Any] = {}
232240
if dag is not None:
233241
data["dag"] = dag
234242
if name is not None:
235243
data["name"] = name
244+
if icon is not None:
245+
data["icon"] = icon
236246

237247
headers = {}
238248
if user_id:
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""Add icon column to pipeline_definition.
2+
3+
Revision ID: 006_add_icon
4+
Revises: 005_user_isolation
5+
Create Date: 2026-02-10 12:00:00.000000
6+
7+
This migration adds an optional icon column to pipeline_definition
8+
for storing a user-selected emoji/icon for UI representation.
9+
"""
10+
11+
import sqlalchemy as sa
12+
from alembic import op
13+
14+
# revision identifiers, used by Alembic.
15+
revision = "006_add_icon"
16+
down_revision = "005_user_isolation"
17+
branch_labels = None
18+
depends_on = None
19+
20+
21+
def upgrade() -> None:
22+
op.add_column(
23+
"pipeline_definition",
24+
sa.Column(
25+
"icon",
26+
sa.String(255),
27+
nullable=True,
28+
comment="Optional icon/emoji for UI representation",
29+
),
30+
)
31+
32+
33+
def downgrade() -> None:
34+
op.drop_column("pipeline_definition", "icon")

services/budpipeline/budpipeline/pipeline/crud.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ async def create(
4747
dag_definition: dict[str, Any],
4848
created_by: str,
4949
description: str | None = None,
50+
icon: str | None = None,
5051
status: PipelineStatus = PipelineStatus.DRAFT,
5152
user_id: UUID | None = None,
5253
system_owned: bool = False,
@@ -58,6 +59,7 @@ async def create(
5859
dag_definition: Complete pipeline DAG definition.
5960
created_by: User or service that created the pipeline.
6061
description: Optional pipeline description.
62+
icon: Optional icon/emoji for UI representation.
6163
status: Initial pipeline status (default: draft).
6264
user_id: UUID of the owning user (None for system/anonymous pipelines).
6365
system_owned: True if this is a system-owned pipeline visible to all users.
@@ -72,6 +74,7 @@ async def create(
7274
definition = PipelineDefinition(
7375
name=name,
7476
description=description,
77+
icon=icon,
7578
dag_definition=dag_definition,
7679
status=status,
7780
step_count=step_count,

services/budpipeline/budpipeline/pipeline/models.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,11 @@ class PipelineDefinition(Base):
102102
nullable=True,
103103
comment="Optional pipeline description",
104104
)
105+
icon: Mapped[str | None] = mapped_column(
106+
String(255),
107+
nullable=True,
108+
comment="Optional icon/emoji for UI representation",
109+
)
105110

106111
# DAG definition
107112
dag_definition: Mapped[dict[str, Any]] = mapped_column(

services/budpipeline/budpipeline/pipeline/routes.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ async def create_pipeline(
9696
name_override=request.name,
9797
created_by="api", # TODO: Get from auth context
9898
description=None,
99+
icon=request.icon,
99100
user_id=user_id,
100101
system_owned=request.system_owned,
101102
)
@@ -108,6 +109,7 @@ async def create_pipeline(
108109
step_count=pipeline["step_count"],
109110
user_id=pipeline.get("user_id"),
110111
system_owned=pipeline.get("system_owned", False),
112+
icon=pipeline.get("icon"),
111113
)
112114
except DuplicatePipelineNameError as e:
113115
raise HTTPException(
@@ -154,6 +156,7 @@ async def list_pipelines(
154156
user_id=p.get("user_id"),
155157
system_owned=p.get("system_owned", False),
156158
description=p.get("description"),
159+
icon=p.get("icon"),
157160
dag=p.get("dag"),
158161
execution_count=p.get("execution_count", 0),
159162
last_execution_at=p.get("last_execution_at"),
@@ -246,6 +249,7 @@ async def update_pipeline(
246249
pipeline_id=pipeline_id,
247250
dag_dict=request.dag,
248251
name_override=request.name,
252+
icon=request.icon,
249253
)
250254
return PipelineResponse(
251255
id=pipeline["id"],
@@ -256,6 +260,7 @@ async def update_pipeline(
256260
step_count=pipeline["step_count"],
257261
user_id=pipeline.get("user_id"),
258262
system_owned=pipeline.get("system_owned", False),
263+
icon=pipeline.get("icon"),
259264
)
260265
except WorkflowNotFoundError:
261266
raise HTTPException(

0 commit comments

Comments
 (0)