Skip to content

Commit

Permalink
feat: pipeline workflow overrides (#1356)
Browse files Browse the repository at this point in the history
* feat: update BitriseYml schema

* feat: add optional basedOn property to related types

* feat: add basedOn prop to WorkflowCard and related components

* fix: can not add new workflow to override
  • Loading branch information
AndrasEszes authored Dec 9, 2024
1 parent 79e5774 commit 95e61e5
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,19 @@ import SortableWorkflowsContext from './components/SortableWorkflowsContext';

type ContentProps = {
id: string;
basedOn?: string;
isCollapsable?: boolean;
containerProps?: CardProps;
};

const WorkflowCardContent = memo(({ id, isCollapsable, containerProps }: ContentProps) => {
const { onCreateWorkflow, onChainWorkflow, onEditWorkflow, onRemoveWorkflow } = useWorkflowActions();
const workflow = useWorkflow(id);
const WorkflowCardContent = memo(({ id, basedOn, isCollapsable, containerProps }: ContentProps) => {
const workflowId = basedOn || id;

const containerRef = useRef(null);
const workflow = useWorkflow(workflowId);
const { data: stacksAndMachines } = useStacksAndMachines();
const { isOpen, onOpen, onToggle } = useDisclosure({
defaultIsOpen: !isCollapsable,
});
const { isOpen, onOpen, onToggle } = useDisclosure({ defaultIsOpen: !isCollapsable });
const { onCreateWorkflow, onChainWorkflow, onEditWorkflow, onRemoveWorkflow } = useWorkflowActions();

const { isSelected } = useSelection();
const isHighlighted = isSelected(id);
Expand Down Expand Up @@ -73,10 +74,10 @@ const WorkflowCardContent = memo(({ id, isCollapsable, containerProps }: Content

<Box display="flex" flexDir="column" alignItems="flex-start" justifyContent="center" flex="1" minW={0}>
<Text textStyle="body/md/semibold" hasEllipsis>
{workflow.userValues.title || id}
{basedOn ? id : workflow.userValues.title || id}
</Text>
<Text textStyle="body/sm/regular" color="text/secondary" hasEllipsis>
{stack.name || 'Unknown stack'}
{basedOn ? `Based on ${basedOn}` : stack.name || 'Unknown stack'}
</Text>
</Box>

Expand Down Expand Up @@ -125,9 +126,13 @@ const WorkflowCardContent = memo(({ id, isCollapsable, containerProps }: Content
<Collapse in={isOpen} transitionEnd={{ enter: { overflow: 'visible' } }} unmountOnExit>
<SortableWorkflowsContext containerRef={containerRef}>
<Box display="flex" flexDir="column" gap="8" p="8" ref={containerRef}>
<ChainedWorkflowList key={`${id}->before_run`} placement="before_run" parentWorkflowId={id} />
<WorkflowStepList workflowId={id} />
<ChainedWorkflowList key={`${id}->after_run`} placement="after_run" parentWorkflowId={id} />
<ChainedWorkflowList
key={`${workflowId}->before_run`}
placement="before_run"
parentWorkflowId={workflowId}
/>
<WorkflowStepList workflowId={workflowId} />
<ChainedWorkflowList key={`${workflowId}->after_run`} placement="after_run" parentWorkflowId={workflowId} />
</Box>
</SortableWorkflowsContext>
</Collapse>
Expand All @@ -143,6 +148,7 @@ type Props = ContentProps & WorkflowActions & StepActions & Selection;

const WorkflowCard = ({
id,
basedOn,
isCollapsable,
containerProps,
selectedWorkflowId = '',
Expand All @@ -154,7 +160,7 @@ const WorkflowCard = ({
selectedStepIndex={selectedStepIndex}
{...actions}
>
<WorkflowCardContent id={id} isCollapsable={isCollapsable} containerProps={containerProps} />
<WorkflowCardContent id={id} basedOn={basedOn} isCollapsable={isCollapsable} containerProps={containerProps} />
</WorkflowCardContextProvider>
);

Expand Down
56 changes: 36 additions & 20 deletions source/javascripts/core/models/BitriseYml.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,26 +266,7 @@ const BitriseYmlSchema = {
workflows: {
patternProperties: {
'.*': {
properties: {
depends_on: {
type: 'array',
items: {
type: 'string',
},
},
abort_on_fail: {
type: 'boolean',
},
should_always_run: {
type: 'string',
enum: ['off', 'workflow'],
},
run_if: {
$ref: '#/definitions/RunIfModel',
},
},
additionalProperties: false,
type: 'object',
$ref: '#/definitions/GraphPipelineWorkflowModel',
},
},
type: 'object',
Expand All @@ -306,6 +287,41 @@ const BitriseYmlSchema = {
additionalProperties: false,
type: 'object',
},
GraphPipelineWorkflowModel: {
properties: {
based_on: {
type: 'string',
},
depends_on: {
items: {
type: 'string',
},
type: 'array',
},
abort_on_fail: {
type: 'boolean',
},
should_always_run: {
type: 'string',
enum: ['off', 'workflow'],
},
run_if: {
$ref: '#/definitions/GraphPipelineWorkflowRunIfModel',
},
},
additionalProperties: false,
type: 'object',
},
GraphPipelineWorkflowRunIfModel: {
required: ['expression'],
properties: {
expression: {
type: 'string',
},
},
additionalProperties: false,
type: 'object',
},
StageModel: {
properties: {
title: {
Expand Down
2 changes: 1 addition & 1 deletion source/javascripts/core/models/BitriseYmlService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@ function addWorkflowToPipeline(
}

if (parentWorkflowId) {
if (!copy.workflows?.[parentWorkflowId] || !copy.pipelines?.[pipelineId]?.workflows?.[parentWorkflowId]) {
if (!copy.pipelines?.[pipelineId]?.workflows?.[parentWorkflowId]) {
return copy;
}
}
Expand Down
2 changes: 1 addition & 1 deletion source/javascripts/core/models/Workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ type WorkflowYmlObject = Workflows[string] & {
};
type Workflow = { id: string; userValues: WorkflowYmlObject };
type ChainedWorkflowPlacement = 'before_run' | 'after_run';
type PipelineWorkflow = { id: string; dependsOn: string[] };
type PipelineWorkflow = { id: string; basedOn?: string; dependsOn: string[] };

export { Workflow, WorkflowYmlObject, Workflows, ChainedWorkflowPlacement, PipelineWorkflow };
20 changes: 20 additions & 0 deletions source/javascripts/pages/PipelinesPage/PipelinesPage.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Meta, StoryObj } from '@storybook/react';
import { Box } from '@bitrise/bitkit';
import { cloneDeep } from 'es-toolkit';
import PipelinesPage from './PipelinesPage';

export default {
Expand Down Expand Up @@ -116,3 +117,22 @@ export const ReactivatePlan: Story = {
};

export const GraphPipelineWithEditing: Story = {};

const withWorkflowOverrideYml = () => {
const yml = cloneDeep(TEST_BITRISE_YML);

if (yml.pipelines?.['graph-pipeline']?.workflows) {
yml.pipelines['graph-pipeline'].workflows.override = {
based_on: 'wf3',
depends_on: ['wf1'],
};
}

return yml;
};

export const WithWorkflowOverride: Story = {
args: {
yml: withWorkflowOverrideYml(),
},
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Edge, Node } from '@xyflow/react';
import { PLACEHOLDER_NODE_TYPE, WORKFLOW_NODE_TYPE } from './GraphPipelineCanvas.const';

export type WorkflowNodeType = Node<{ fixed?: boolean; highlighted?: boolean }>;
export type WorkflowNodeType = Node<{ basedOn?: string; fixed?: boolean; highlighted?: boolean }>;
export type PlaceholderNodeType = Node<{ dependsOn: string[] }>;
export type GraphPipelineEdgeType = Edge<{ highlighted?: boolean }>;
export type GraphPipelineNodeType = PlaceholderNodeType | WorkflowNodeType;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,19 @@ const selectedStyle = {
outlineOffset: '-2px',
} satisfies CardProps;

const WorkflowNode = ({ id, zIndex, selected }: Props) => {
const WorkflowNode = ({ id, selected, zIndex, data }: Props) => {
const ref = useRef<HTMLDivElement>(null);
const hovered = useHover(ref);
const workflows = useWorkflows();
const { selectedPipeline } = usePipelineSelector();

const openDialog = usePipelinesPageStore((s) => s.openDialog);
const closeDialog = usePipelinesPageStore((s) => s.closeDialog);
const selectedWorkflowId = usePipelinesPageStore((s) => s.workflowId);
const selectedStepIndex = usePipelinesPageStore((s) => s.stepIndex);
const setStepIndex = usePipelinesPageStore((s) => s.setStepIndex);

const { selectedPipeline } = usePipelineSelector();
const selectedStepIndex = usePipelinesPageStore((s) => s.stepIndex);
const selectedWorkflowId = usePipelinesPageStore((s) => s.workflowId);
const isGraphPipelinesEnabled = useFeatureFlag('enable-dag-pipelines');

const { updateNode, deleteElements, setEdges } = useReactFlow<GraphPipelineNodeType, GraphPipelineEdgeType>();

const { moveStep, cloneStep, deleteStep, upgradeStep, setChainedWorkflows, removeChainedWorkflow } =
Expand All @@ -61,6 +62,8 @@ const WorkflowNode = ({ id, zIndex, selected }: Props) => {
onResize: ({ height }) => updateNode(id, { height }),
});

const basedOn = 'basedOn' in data ? data.basedOn : undefined;

const {
handleAddStep,
handleMoveStep,
Expand Down Expand Up @@ -131,6 +134,15 @@ const WorkflowNode = ({ id, zIndex, selected }: Props) => {
}
}

if (basedOn) {
return {
handleRemoveWorkflow: (deletedWorkflowId: string) => {
deleteElements({ nodes: [{ id: deletedWorkflowId }] });
handleWorkflowActionDialogChange(deletedWorkflowId, 'remove');
},
};
}

return {
handleAddStep: (workflowId: string, stepIndex: number) =>
openDialog({
Expand Down Expand Up @@ -206,21 +218,22 @@ const WorkflowNode = ({ id, zIndex, selected }: Props) => {
},
};
}, [
basedOn,
workflows,
selectedPipeline,
selectedStepIndex,
selectedWorkflowId,
isGraphPipelinesEnabled,
moveStep,
cloneStep,
upgradeStep,
openDialog,
selectedPipeline,
deleteStep,
selectedWorkflowId,
selectedStepIndex,
closeDialog,
setStepIndex,
deleteElements,
removeChainedWorkflow,
workflows,
setChainedWorkflows,
removeChainedWorkflow,
]);

const containerProps = useMemo(
Expand Down Expand Up @@ -252,9 +265,10 @@ const WorkflowNode = ({ id, zIndex, selected }: Props) => {
<WorkflowCard
id={id}
isCollapsable
basedOn={basedOn}
containerProps={containerProps}
selectedWorkflowId={selectedWorkflowId}
selectedStepIndex={selectedStepIndex}
selectedWorkflowId={selectedWorkflowId}
onAddStep={handleAddStep}
onMoveStep={handleMoveStep}
onCloneStep={handleCloneStep}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const usePipelineWorkflows = (): PipelineWorkflow[] => {
return Object.entries(pipelineWorkflows).map(([id, pipelineWorkflow]) => {
return {
id,
basedOn: pipelineWorkflow.based_on,
dependsOn: pipelineWorkflow.depends_on ?? [],
} satisfies PipelineWorkflow;
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { GraphPipelineNodeType } from '../GraphPipelineCanvas.types';
function createWorkflowNode(workflow: PipelineWorkflow, actionable: boolean) {
return {
id: workflow.id,
data: {},
data: { basedOn: workflow.basedOn },
deletable: actionable,
draggable: false,
focusable: false,
Expand Down

0 comments on commit 95e61e5

Please sign in to comment.