From 266a80bed312400b41958602f4f35c2ce0fdf485 Mon Sep 17 00:00:00 2001 From: flakronademi Date: Mon, 13 Jul 2026 14:28:40 +0200 Subject: [PATCH 1/5] added Create Pipeline button and template modal to file explorer - Show "Create Pipeline" button at the bottom of the file explorer when no .rosetta pipeline files exist; hides automatically once a pipeline is created - Add CreatePipelineModal with two selectable templates: "Getting Started" (deps + test + teardown) and "Full CI/CD" (deps + run + test + freshness + deploy + teardown) - Auto-open the newly created pipeline.yml in the editor on creation --- .../modals/createPipelineModal/index.tsx | 358 ++++++++++++++++++ src/renderer/components/modals/index.ts | 1 + .../components/sidebar/project-sidebar.tsx | 115 +++++- 3 files changed, 459 insertions(+), 15 deletions(-) create mode 100644 src/renderer/components/modals/createPipelineModal/index.tsx diff --git a/src/renderer/components/modals/createPipelineModal/index.tsx b/src/renderer/components/modals/createPipelineModal/index.tsx new file mode 100644 index 00000000..1818fe5f --- /dev/null +++ b/src/renderer/components/modals/createPipelineModal/index.tsx @@ -0,0 +1,358 @@ +import React from 'react'; +import { + Box, + Button, + Typography, + Stack, + Paper, + alpha, + CircularProgress, + Chip, +} from '@mui/material'; +import { + Close, + AccountTree, + CheckCircleOutline, + PlayArrow, + Code, +} from '@mui/icons-material'; +import { useTheme } from '@mui/material/styles'; +import { toast } from 'react-toastify'; +import { Modal } from '../modal'; +import { projectsServices } from '../../../services'; +import { Project } from '../../../../types/backend'; + +interface PipelineTemplate { + id: string; + label: string; + description: string; + badge?: string; + steps: string[]; + content: string; +} + +const TEMPLATES: PipelineTemplate[] = [ + { + id: 'getting-started', + label: 'Getting Started', + badge: 'Recommended', + description: + 'A simple pipeline that runs dbt deps, dbt test, and a teardown step. Perfect for new projects.', + steps: ['dbt deps', 'dbt test', 'teardown'], + content: `name: "CI" +jobs: + - name: "setup" + steps: + - name: Install dependencies + plugin: dbt@v1 + command: dbt deps + - name: "run-dbt-command" + steps: + - name: dbt test + plugin: dbt@v1 + command: dbt test + - name: "teardown" + type: "cleanup" + steps: + - name: Run teardown + plugin: command@v1 + command: echo "TEARDOWN" +`, + }, + { + id: 'full-ci', + label: 'Full CI/CD', + badge: 'Advanced', + description: + 'A complete pipeline with dependency install, model runs, tests, freshness checks, and a deploy notification step.', + steps: [ + 'dbt deps', + 'dbt run', + 'dbt test', + 'dbt source freshness', + 'deploy', + ], + content: `name: "Full CI/CD" +jobs: + - name: "setup" + steps: + - name: Install dependencies + plugin: dbt@v1 + command: dbt deps + - name: "build" + steps: + - name: Run models + plugin: dbt@v1 + command: dbt run + - name: "test" + steps: + - name: Run tests + plugin: dbt@v1 + command: dbt test + - name: Check source freshness + plugin: dbt@v1 + command: dbt source freshness + - name: "deploy" + steps: + - name: Notify deployment + plugin: command@v1 + command: echo "Deployment complete" + - name: "teardown" + type: "cleanup" + steps: + - name: Run teardown + plugin: command@v1 + command: echo "TEARDOWN" +`, + }, +]; + +interface CreatePipelineModalProps { + isOpen: boolean; + onClose: () => void; + project: Project; + onCreated: (filePath: string) => void; +} + +export const CreatePipelineModal: React.FC = ({ + isOpen, + onClose, + project, + onCreated, +}) => { + const theme = useTheme(); + const [selectedTemplateId, setSelectedTemplateId] = React.useState< + string | null + >(null); + const [isCreating, setIsCreating] = React.useState(false); + + // Reset selection when modal opens + React.useEffect(() => { + if (isOpen) { + setSelectedTemplateId(null); + } + }, [isOpen]); + + const handleCreate = async () => { + if (!selectedTemplateId) return; + + const template = TEMPLATES.find((t) => t.id === selectedTemplateId); + if (!template) return; + + setIsCreating(true); + try { + // Ensure .rosetta directory exists + await projectsServices.createFolder({ + filePath: project.path, + name: '.rosetta', + }); + + const pipelinePath = `${project.path}/.rosetta/pipeline.yml`; + await projectsServices.saveFileContent({ + path: pipelinePath, + content: template.content, + }); + + toast.success('Pipeline created successfully.'); + onCreated(pipelinePath); + onClose(); + } catch (error) { + const msg = + error instanceof Error ? error.message : 'Failed to create pipeline'; + toast.error(msg); + } finally { + setIsCreating(false); + } + }; + + return ( + + + {/* Header description */} + + Choose a template to scaffold your pipeline. The file will be created + at .rosetta/pipeline.yml in your project. + + + {/* Template cards */} + + {TEMPLATES.map((template) => { + const isSelected = selectedTemplateId === template.id; + return ( + setSelectedTemplateId(template.id)} + sx={{ + p: 2, + cursor: 'pointer', + border: '1px solid', + borderColor: isSelected + ? 'primary.main' + : theme.palette.divider, + bgcolor: isSelected + ? alpha( + theme.palette.primary.main, + theme.palette.mode === 'dark' ? 0.1 : 0.04, + ) + : 'background.paper', + transition: 'border-color 0.15s, background-color 0.15s', + '&:hover': { + borderColor: 'primary.main', + bgcolor: alpha( + theme.palette.primary.main, + theme.palette.mode === 'dark' ? 0.08 : 0.03, + ), + }, + }} + > + + {/* Icon */} + + + + + {/* Content */} + + + + {template.label} + + {template.badge && ( + + )} + + + {template.description} + + + {/* Steps preview */} + + {template.steps.map((step) => ( + + + + {step} + + + ))} + + + + {/* Checkmark */} + {isSelected && ( + + )} + + + ); + })} + + + {/* Footer actions */} + + + + + + + ); +}; diff --git a/src/renderer/components/modals/index.ts b/src/renderer/components/modals/index.ts index 4f5d776e..a602eb79 100644 --- a/src/renderer/components/modals/index.ts +++ b/src/renderer/components/modals/index.ts @@ -17,4 +17,5 @@ export * from './pushToCloudModal'; export * from './rawLayerModal'; export * from './removeConnectionModal'; export * from './pipelineSelectorModal'; +export * from './createPipelineModal'; export * from './sshPassphraseModal'; diff --git a/src/renderer/components/sidebar/project-sidebar.tsx b/src/renderer/components/sidebar/project-sidebar.tsx index 3999e4df..cc5a00c1 100644 --- a/src/renderer/components/sidebar/project-sidebar.tsx +++ b/src/renderer/components/sidebar/project-sidebar.tsx @@ -8,6 +8,7 @@ import { CardContent, Chip, Divider, + Tooltip, } from '@mui/material'; import { Add, @@ -15,8 +16,9 @@ import { DeleteOutline, Storage as DatabaseIcon, SwapHoriz, + AccountTree, } from '@mui/icons-material'; -import { useTheme } from '@mui/material/styles'; +import { useTheme, alpha } from '@mui/material/styles'; import { FileTreeViewer } from '../index'; import { FileTreeContainer } from '../../screens/projectDetails/styles'; import { @@ -27,6 +29,8 @@ import { } from '../../../types/backend'; import { SourceControlView } from '../sourceControl'; import connectionIcons from '../../../../assets/connectionIcons'; +import { useListPipelines } from '../../controllers'; +import { CreatePipelineModal } from '../modals'; export type SidebarTab = 'explorer' | 'scm' | 'connections'; @@ -252,6 +256,7 @@ interface ExplorerTabProps { statuses: FileStatus[]; isLoadingDirectories: boolean; selectedFilePath?: string; + project?: Project; onDeleteFile: (deletedFile: string) => void; onFileSelect: (fileNode: any) => Promise; onRefreshFiles: () => Promise; @@ -266,6 +271,7 @@ const ExplorerTab: React.FC = ({ statuses, isLoadingDirectories, selectedFilePath, + project, onDeleteFile, onFileSelect, onRefreshFiles, @@ -274,21 +280,99 @@ const ExplorerTab: React.FC = ({ onRenameFile, onRunPipeline, }) => { + const theme = useTheme(); + const [createPipelineOpen, setCreatePipelineOpen] = React.useState(false); + + const { data: pipelines = [], refetch: refetchPipelines } = useListPipelines( + project?.id, + ); + + const hasPipelines = pipelines.length > 0; + + const handlePipelineCreated = React.useCallback( + async (filePath: string) => { + await refetchPipelines(); + await onRefreshFiles(); + // Open the newly created pipeline file in the editor + onFileSelect({ + id: filePath, + name: 'pipeline.yml', + path: filePath, + type: 'file' as const, + }); + }, + [refetchPipelines, onRefreshFiles, onFileSelect], + ); + return ( - - {directories && ( - + {/* Tree takes all remaining space */} + + {directories && ( + + )} + + + {/* Create Pipeline button — only rendered (and takes space) when no pipelines exist */} + {project && !hasPipelines && ( + + + + + + )} + + {project && ( + setCreatePipelineOpen(false)} + project={project} + onCreated={(filePath) => handlePipelineCreated(filePath)} /> )} @@ -405,6 +489,7 @@ export const ProjectSidebar: React.FC = ({ statuses={statuses} isLoadingDirectories={isLoadingDirectories} selectedFilePath={selectedFilePath} + project={project} onDeleteFile={onDeleteFile} onFileSelect={onFileSelect} onRefreshFiles={onRefreshFiles} From becf0c585d744a3727be947d7a1830d95d840738 Mon Sep 17 00:00:00 2001 From: flakronademi Date: Thu, 16 Jul 2026 10:36:53 +0200 Subject: [PATCH 2/5] made Create Pipeline button always visible in sidebar instead of conditional on missing pipelines and changed button styling from outlined dashed to contained variant --- .../modals/createPipelineModal/index.tsx | 27 ++++++++++++++++--- .../components/sidebar/project-sidebar.tsx | 17 +++--------- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/src/renderer/components/modals/createPipelineModal/index.tsx b/src/renderer/components/modals/createPipelineModal/index.tsx index 1818fe5f..535fc813 100644 --- a/src/renderer/components/modals/createPipelineModal/index.tsx +++ b/src/renderer/components/modals/createPipelineModal/index.tsx @@ -28,6 +28,7 @@ interface PipelineTemplate { description: string; badge?: string; steps: string[]; + fileName: string; content: string; } @@ -39,6 +40,7 @@ const TEMPLATES: PipelineTemplate[] = [ description: 'A simple pipeline that runs dbt deps, dbt test, and a teardown step. Perfect for new projects.', steps: ['dbt deps', 'dbt test', 'teardown'], + fileName: 'pipeline.yml', content: `name: "CI" jobs: - name: "setup" @@ -72,6 +74,7 @@ jobs: 'dbt source freshness', 'deploy', ], + fileName: 'pipeline-full-ci.yml', content: `name: "Full CI/CD" jobs: - name: "setup" @@ -147,7 +150,7 @@ export const CreatePipelineModal: React.FC = ({ name: '.rosetta', }); - const pipelinePath = `${project.path}/.rosetta/pipeline.yml`; + const pipelinePath = `${project.path}/.rosetta/${template.fileName}`; await projectsServices.saveFileContent({ path: pipelinePath, content: template.content, @@ -175,8 +178,8 @@ export const CreatePipelineModal: React.FC = ({ {/* Header description */} - Choose a template to scaffold your pipeline. The file will be created - at .rosetta/pipeline.yml in your project. + Choose a template to scaffold your pipeline. Each template creates its + own file inside .rosetta/ in your project. {/* Template cards */} @@ -187,7 +190,9 @@ export const CreatePipelineModal: React.FC = ({ setSelectedTemplateId(template.id)} + onClick={() => { + setSelectedTemplateId(template.id); + }} sx={{ p: 2, cursor: 'pointer', @@ -270,6 +275,20 @@ export const CreatePipelineModal: React.FC = ({ {template.description} + {/* File name */} + + .rosetta/{template.fileName} + + {/* Steps preview */} = ({ )} - {/* Create Pipeline button — only rendered (and takes space) when no pipelines exist */} - {project && !hasPipelines && ( + {/* Create Pipeline button — always visible */} + {project && ( = ({ }} > - - + + ); }; From 3b24a7bb11057a6b3d1cd81a6a5a372058564418 Mon Sep 17 00:00:00 2001 From: flakronademi Date: Thu, 16 Jul 2026 14:55:28 +0200 Subject: [PATCH 5/5] renamed getting started pipeline from pipeline.yml to pipeline-getting-started.yml --- src/renderer/components/modals/createPipelineModal/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/components/modals/createPipelineModal/index.tsx b/src/renderer/components/modals/createPipelineModal/index.tsx index b76274df..1532174d 100644 --- a/src/renderer/components/modals/createPipelineModal/index.tsx +++ b/src/renderer/components/modals/createPipelineModal/index.tsx @@ -45,7 +45,7 @@ const TEMPLATES: PipelineTemplate[] = [ description: 'A simple pipeline that runs dbt deps, dbt test, and a teardown step. Perfect for new projects.', steps: ['dbt deps', 'dbt test', 'teardown'], - fileName: 'pipeline.yml', + fileName: 'pipeline-getting-started.yml', content: `name: "CI" jobs: - name: "setup"