Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,52 @@ steps: []`;
expect(result).toContain('enabled: false');
});

it('should preserve comments, blank lines, and formatting when toggling enabled', () => {
const yaml = `# Workflow configuration
name: Test Workflow

# Whether the workflow is active
enabled: false

steps:
# Create a Jira ticket
- name: step1
type: jira
params:
summary: test`;
const workflow: Partial<EsWorkflow> = { enabled: true };

const result = updateWorkflowYamlFields(yaml, workflow, true);

expect(result).toContain('enabled: true');
expect(result).not.toContain('enabled: false');
expect(result).toContain('# Workflow configuration');
expect(result).toContain('# Whether the workflow is active');
expect(result).toContain('# Create a Jira ticket');
const blankLineCount = (result.match(/\n\n/g) || []).length;
expect(blankLineCount).toBeGreaterThanOrEqual(2);
expect(result).toContain('name: Test Workflow');
expect(result).toContain('summary: test');
});

it('should preserve quoted template expressions when toggling enabled', () => {
const yaml = `name: Test Workflow
enabled: false
steps:
- name: step1
type: jira
params:
comment: "{{ inputs.comment }}"
summary: "{{ inputs.summary }}"`;
const workflow: Partial<EsWorkflow> = { enabled: true };

const result = updateWorkflowYamlFields(yaml, workflow, true);

expect(result).toContain('enabled: true');
expect(result).toContain('comment: "{{ inputs.comment }}"');
expect(result).toContain('summary: "{{ inputs.summary }}"');
});

it('should use enabledValue parameter when provided', () => {
const yaml = 'name: Test Workflow\nenabled: true\nsteps: []';
const workflow: Partial<EsWorkflow> = { enabled: true }; // Request to enable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,64 @@ steps:
expect(result).toContain('- name: step1');
});

it('should preserve comments, blank lines, and formatting when toggling enabled', () => {
const yaml = `# Workflow configuration
name: Test Workflow
description: A workflow that does things

# Whether the workflow is active
enabled: false

steps:
# Create a Jira ticket
- name: step1
type: jira
params:
summary: test

# Notify the user
- name: step2
type: slack
params:
channel: general`;

const result = updateYamlField(yaml, 'enabled', true);

// Only the enabled value should change
expect(result).toContain('enabled: true');
expect(result).not.toContain('enabled: false');

// All comments preserved
expect(result).toContain('# Workflow configuration');
expect(result).toContain('# Whether the workflow is active');
expect(result).toContain('# Create a Jira ticket');
expect(result).toContain('# Notify the user');

// Blank lines preserved
const blankLineCount = (result.match(/\n\n/g) || []).length;
expect(blankLineCount).toBeGreaterThanOrEqual(3);

// Rest of content unchanged
expect(result).toContain('name: Test Workflow');
expect(result).toContain('description: A workflow that does things');
expect(result).toContain('channel: general');
});

it('should preserve template expressions like {{ inputs.comment }}', () => {
const yaml = `name: Test Workflow
enabled: false
steps:
- name: step1
type: jira
params:
comment: "{{ inputs.comment }}"`;

const result = updateYamlField(yaml, 'enabled', true);

expect(result).toContain('enabled: true');
expect(result).toContain('comment: "{{ inputs.comment }}"');
});

it('should handle nested field paths with dot notation', () => {
const yaml = `metadata:
version: 1.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1486,6 +1486,76 @@ steps:
})
);
});

it('should preserve YAML comments, formatting, and template expressions when toggling enabled', async () => {
const mockRequest = {
auth: {
credentials: { username: 'test-user' },
},
} as any;

const yamlWithComments = `# Workflow configuration
name: Test Workflow
description: A test workflow

# Whether the workflow is active
enabled: false

triggers:
- type: manual

steps:
# Create a Jira ticket
- type: console
name: first-step
with:
message: "{{ inputs.comment }}"`;

const existingDoc = {
_id: 'test-workflow-id',
_source: {
...mockWorkflowDocument._source,
enabled: false,
yaml: yamlWithComments,
definition: {
name: 'Test Workflow',
enabled: false,
triggers: [{ type: 'manual' }],
steps: [
{
type: 'console',
name: 'first-step',
with: { message: '{{ inputs.comment }}' },
},
],
},
},
};

mockEsClient.search.mockResolvedValue({ hits: { hits: [existingDoc] } } as any);

// Toggle enabled without providing yaml (metadata-only update)
await service.updateWorkflow('test-workflow-id', { enabled: true }, 'default', mockRequest);

const indexCall = mockEsClient.index.mock.calls[0][0] as any;
const savedYaml = indexCall.document.yaml;

// enabled should be toggled
expect(savedYaml).toContain('enabled: true');
expect(savedYaml).not.toContain('enabled: false');

// Comments should be preserved
expect(savedYaml).toContain('# Workflow configuration');
expect(savedYaml).toContain('# Whether the workflow is active');
expect(savedYaml).toContain('# Create a Jira ticket');

// Template expressions should not be corrupted
expect(savedYaml).toContain('{{ inputs.comment }}');
expect(savedYaml).not.toContain('null');

// Blank lines should be preserved
expect((savedYaml.match(/\n\n/g) || []).length).toBeGreaterThanOrEqual(2);
});
});

describe('deleteWorkflows', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,7 @@ import {
} from '../../common/lib/errors';

import { validateStepNameUniqueness } from '../../common/lib/validate_step_names';
import {
parseWorkflowYamlToJSON,
parseYamlToJSONWithoutValidation,
stringifyWorkflowDefinition,
} from '../../common/lib/yaml';
import { parseWorkflowYamlToJSON, updateWorkflowYamlFields } from '../../common/lib/yaml';
import { getWorkflowZodSchema } from '../../common/schema';
import { getAuthenticatedUser } from '../lib/get_user';
import { hasScheduledTriggers } from '../lib/schedule_utils';
Expand Down Expand Up @@ -531,23 +527,17 @@ export class WorkflowsService {
}

if (yamlUpdated && existingDocument._source?.yaml) {
const originalYamlParse = parseYamlToJSONWithoutValidation(existingDocument._source.yaml);
const baseDefinition = originalYamlParse.success
? originalYamlParse.json
: existingDocument._source?.definition;

if (baseDefinition) {
const fieldUpdates = {
...(workflow.name !== undefined && { name: workflow.name }),
...(workflow.enabled !== undefined && { enabled: updatedData.enabled }),
...(workflow.description !== undefined && { description: workflow.description }),
...(workflow.tags !== undefined && { tags: workflow.tags }),
};
updatedData.yaml = stringifyWorkflowDefinition({
...baseDefinition,
...fieldUpdates,
});
}
// Use in-place YAML field updates to preserve formatting, comments,
// and template expressions that would be corrupted by a parse-to-JSON then re-stringify cycle.
// `enabledValue` is passed separately because the server may override
// the requested value (e.g. force `false` when the workflow has no valid
// definition). Other fields (name, description, tags) are read directly
// from the `workflow` object inside `updateWorkflowYamlFields`.
updatedData.yaml = updateWorkflowYamlFields(
existingDocument._source.yaml,
workflow,
updatedData.enabled
);
}
}

Expand Down
Loading