From dd8e65eb54c8d36536c2f2d13e6bdebe545d0600 Mon Sep 17 00:00:00 2001 From: Yngrid Coello Date: Sun, 31 May 2026 23:22:32 +0200 Subject: [PATCH 1/2] filter run modal using workflow triggers --- .../ui/workflow_execute_modal.test.tsx | 22 +++- .../ui/workflow_execute_modal.tsx | 29 +++-- .../ui/workflow_execute_modal_helpers.test.ts | 56 +++++++++- .../ui/workflow_execute_modal_helpers.ts | 100 +++++++++++++----- 4 files changed, 167 insertions(+), 40 deletions(-) diff --git a/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_modal.test.tsx b/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_modal.test.tsx index 9018de2a5f3e4..5ceeb125ca100 100644 --- a/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_modal.test.tsx +++ b/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_modal.test.tsx @@ -154,7 +154,7 @@ describe('WorkflowExecuteModal', () => { expect(getByText('Run Workflow')).toBeInTheDocument(); }); - it('renders all trigger type buttons', () => { + it('renders all trigger type buttons when the workflow definition is missing', () => { const { getByText } = renderWithProviders( { expect(getByText('Historical')).toBeInTheDocument(); }); + it('shows only tabs that match triggers declared in the workflow', () => { + const { getByText, queryByText } = renderWithProviders( + + ); + + expect(getByText('Alert')).toBeInTheDocument(); + expect(getByText('Historical')).toBeInTheDocument(); + expect(queryByText('Document')).not.toBeInTheDocument(); + expect(queryByText('Event')).not.toBeInTheDocument(); + expect(queryByText('Manual')).not.toBeInTheDocument(); + }); + it('uses the test run title and still exposes the full trigger tab set', () => { const { getByText } = renderWithProviders( ( [definition, yamlString] ); + const visibleTriggerTabs = useMemo( + () => getVisibleWorkflowTriggerTabs(definition, normalizedInputs), + [definition, normalizedInputs] + ); + const [selectedTrigger, setSelectedTrigger] = useState(() => resolveInitialSelectedTrigger( definition, @@ -213,16 +219,18 @@ export const WorkflowExecuteModal = React.memo( useEffect(() => { setSelectedTrigger((current) => { + let next = current; if (current === 'alert' && !hasAlertRacAccess) { - return getFallbackTriggerTab(normalizedInputs, definition, canReadWorkflowExecution); - } - if (current === 'historical' && !canReadWorkflowExecution) { - return getFallbackTriggerTab(normalizedInputs, definition, canReadWorkflowExecution); - } - if (current === 'event' && (!canReadWorkflowExecution || !eventDrivenExecutionEnabled)) { - return getFallbackTriggerTab(normalizedInputs, definition, canReadWorkflowExecution); + next = getFallbackTriggerTab(normalizedInputs, definition, canReadWorkflowExecution); + } else if (current === 'historical' && !canReadWorkflowExecution) { + next = getFallbackTriggerTab(normalizedInputs, definition, canReadWorkflowExecution); + } else if ( + current === 'event' && + (!canReadWorkflowExecution || !eventDrivenExecutionEnabled) + ) { + next = getFallbackTriggerTab(normalizedInputs, definition, canReadWorkflowExecution); } - return current; + return ensureSelectedTriggerTabVisible(next, visibleTriggerTabs); }); }, [ hasAlertRacAccess, @@ -230,6 +238,7 @@ export const WorkflowExecuteModal = React.memo( eventDrivenExecutionEnabled, normalizedInputs, definition, + visibleTriggerTabs, ]); if (shouldAutoRun) { @@ -363,7 +372,7 @@ export const WorkflowExecuteModal = React.memo( `} > - {ENABLED_TRIGGER_TABS.map((trigger) => { + {visibleTriggerTabs.map((trigger) => { let triggerDisabledTooltip: string | undefined; if (trigger === 'alert' && !hasAlertRacAccess) { triggerDisabledTooltip = alertTabDisabledTooltip; diff --git a/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_modal_helpers.test.ts b/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_modal_helpers.test.ts index 111e639a28673..c5dcf09ac38a9 100644 --- a/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_modal_helpers.test.ts +++ b/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_modal_helpers.test.ts @@ -14,6 +14,7 @@ import { buildDefaultTriggerEventSearchQuery, buildWorkflowTriggerScopeKql, getFallbackTriggerTab, + getVisibleWorkflowTriggerTabs, getWorkflowCustomTriggerTypeIds, hasCustomEventTrigger, resolveInitialSelectedTrigger, @@ -139,6 +140,57 @@ describe('buildDefaultTriggerEventSearchQuery', () => { }); }); +describe('getVisibleWorkflowTriggerTabs', () => { + it('returns all tabs when the workflow has no triggers', () => { + expect(getVisibleWorkflowTriggerTabs(null, undefined)).toEqual([ + 'alert', + 'index', + 'event', + 'manual', + 'historical', + ]); + }); + + it('returns alert and historical for alert-only workflows', () => { + expect( + getVisibleWorkflowTriggerTabs({ ...baseDefinition, triggers: [{ type: 'alert' }] }, undefined) + ).toEqual(['alert', 'historical']); + }); + + it('returns document and historical for manual-only workflows without inputs', () => { + expect( + getVisibleWorkflowTriggerTabs( + { ...baseDefinition, triggers: [{ type: 'manual' }] }, + undefined + ) + ).toEqual(['index', 'historical']); + }); + + it('returns manual, document, and historical when manual trigger defines inputs', () => { + const normalizedWithOneField = normalizeFieldsToJsonSchema([ + { name: 'x', type: 'string', required: true }, + ]); + expect( + getVisibleWorkflowTriggerTabs( + { + ...baseDefinition, + triggers: [{ type: 'manual', inputs: [{ name: 'x', type: 'string', required: true }] }], + } as WorkflowYaml, + normalizedWithOneField + ) + ).toEqual(['index', 'manual', 'historical']); + }); + + it('returns event and historical for custom event-driven workflows', () => { + expect( + getVisibleWorkflowTriggerTabs( + workflowWithExtensionTriggers([{ type: 'cases.created' }]), + undefined + ) + ).toEqual(['event', 'historical']); + }); +}); + describe('getFallbackTriggerTab', () => { const normalizedWithOneField: NormalizedWorkflowInputs = normalizeFieldsToJsonSchema([ { name: 'x', type: 'string', required: true }, @@ -174,9 +226,9 @@ describe('resolveInitialSelectedTrigger', () => { ); }); - it('falls back when custom triggers exist but execution read is denied', () => { + it('falls back to the first visible tab when custom triggers exist but execution read is denied', () => { expect(resolveInitialSelectedTrigger(customOnly, undefined, true, false, undefined)).toBe( - 'index' + 'event' ); }); diff --git a/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_modal_helpers.ts b/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_modal_helpers.ts index 71acb42bce955..57bd933ab5628 100644 --- a/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_modal_helpers.ts +++ b/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_modal_helpers.ts @@ -12,6 +12,7 @@ import type { WorkflowYaml } from '@kbn/workflows'; import { isTriggerType } from '@kbn/workflows'; import { getInputsFromDefinition } from '@kbn/workflows/spec/lib/field_conversion'; import type { JsonModelSchemaType } from '@kbn/workflows/spec/schema/common/json_model_schema'; +import { ENABLED_TRIGGER_TABS } from './constants'; import type { WorkflowTriggerTab } from './types'; export type NormalizedWorkflowInputs = JsonModelSchemaType | undefined; @@ -52,6 +53,51 @@ export function isRacAlertsApiForbiddenError(error: unknown): boolean { ); } +export function workflowDefinitionHasTriggerType( + definition: WorkflowYaml | null, + triggerType: string +): boolean { + return Boolean(definition?.triggers?.some((trigger) => trigger.type === triggerType)); +} + +/** Run-modal tabs to show based on triggers declared in the workflow definition. */ +export function getVisibleWorkflowTriggerTabs( + definition: WorkflowYaml | null, + normalizedInputs: NormalizedWorkflowInputs | undefined +): readonly WorkflowTriggerTab[] { + if (!definition?.triggers?.length) { + return ENABLED_TRIGGER_TABS; + } + + const visible: WorkflowTriggerTab[] = []; + + if (workflowDefinitionHasTriggerType(definition, 'alert')) { + visible.push('alert'); + } + if (hasCustomEventTrigger(definition)) { + visible.push('event'); + } + if (workflowDefinitionHasTriggerType(definition, 'manual')) { + visible.push('index'); + } + if (hasWorkflowInputFields(normalizedInputs)) { + visible.push('manual'); + } + visible.push('historical'); + + return visible; +} + +export function ensureSelectedTriggerTabVisible( + selected: WorkflowTriggerTab, + visibleTabs: readonly WorkflowTriggerTab[] +): WorkflowTriggerTab { + if (visibleTabs.includes(selected)) { + return selected; + } + return visibleTabs[0] ?? 'historical'; +} + export function hasCustomEventTrigger(definition: WorkflowYaml | null): boolean { if (!definition?.triggers?.length) { return false; @@ -147,36 +193,36 @@ export function resolveInitialSelectedTrigger( canReadWorkflowExecution: boolean, normalizedInputs: NormalizedWorkflowInputs | undefined ): WorkflowTriggerTab { - if (initialExecutionId) { - return canReadWorkflowExecution - ? 'historical' - : getFallbackTriggerTab(normalizedInputs, definition, canReadWorkflowExecution); - } + const visibleTabs = getVisibleWorkflowTriggerTabs(definition, normalizedInputs); - const hasAlertTrigger = Boolean(definition?.triggers?.some((t) => t.type === 'alert')); - const hasEventTrigger = hasCustomEventTrigger(definition); + let selected: WorkflowTriggerTab; - if (hasAlertTrigger) { - return hasAlertRacAccess - ? 'alert' + if (initialExecutionId) { + selected = canReadWorkflowExecution + ? 'historical' : getFallbackTriggerTab(normalizedInputs, definition, canReadWorkflowExecution); + } else { + const hasAlertTrigger = workflowDefinitionHasTriggerType(definition, 'alert'); + const hasEventTrigger = hasCustomEventTrigger(definition); + + if (hasAlertTrigger) { + selected = hasAlertRacAccess + ? 'alert' + : getFallbackTriggerTab(normalizedInputs, definition, canReadWorkflowExecution); + } else if (hasEventTrigger && canReadWorkflowExecution) { + selected = 'event'; + } else if (hasEventTrigger && !canReadWorkflowExecution) { + selected = getFallbackTriggerTab(normalizedInputs, definition, false); + } else if (hasWorkflowInputFields(normalizedInputs)) { + selected = getFallbackTriggerTab(normalizedInputs, definition, canReadWorkflowExecution); + } else { + const preferred = getDefaultTrigger(definition); + selected = + preferred === 'alert' && !hasAlertRacAccess + ? getFallbackTriggerTab(normalizedInputs, definition, canReadWorkflowExecution) + : preferred; + } } - if (hasEventTrigger && canReadWorkflowExecution) { - return 'event'; - } - - if (hasEventTrigger && !canReadWorkflowExecution) { - return getFallbackTriggerTab(normalizedInputs, definition, false); - } - - if (hasWorkflowInputFields(normalizedInputs)) { - return getFallbackTriggerTab(normalizedInputs, definition, canReadWorkflowExecution); - } - - const preferred = getDefaultTrigger(definition); - if (preferred === 'alert' && !hasAlertRacAccess) { - return getFallbackTriggerTab(normalizedInputs, definition, canReadWorkflowExecution); - } - return preferred; + return ensureSelectedTriggerTabVisible(selected, visibleTabs); } From bb2dfe2f513ec3becc827773cab9a6532e693f58 Mon Sep 17 00:00:00 2001 From: Yngrid Coello Date: Mon, 1 Jun 2026 09:44:59 +0200 Subject: [PATCH 2/2] Updated failing test for Execution modal tabs --- .../ui/workflow_execute_modal.test.tsx | 2 +- .../ui/workflow_execute_modal.tsx | 4 +- .../ui/workflow_execute_modal_helpers.test.ts | 39 +++++-------------- .../ui/workflow_execute_modal_helpers.ts | 10 ++--- .../fixtures/workflows/console_workflows.ts | 16 ++++++++ .../ui/fixtures/workflows/index.ts | 1 + .../workflow_execution/test_run.spec.ts | 10 ++++- 7 files changed, 40 insertions(+), 42 deletions(-) diff --git a/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_modal.test.tsx b/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_modal.test.tsx index 5ceeb125ca100..c71f121129795 100644 --- a/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_modal.test.tsx +++ b/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_modal.test.tsx @@ -185,10 +185,10 @@ describe('WorkflowExecuteModal', () => { ); expect(getByText('Alert')).toBeInTheDocument(); + expect(getByText('Manual')).toBeInTheDocument(); expect(getByText('Historical')).toBeInTheDocument(); expect(queryByText('Document')).not.toBeInTheDocument(); expect(queryByText('Event')).not.toBeInTheDocument(); - expect(queryByText('Manual')).not.toBeInTheDocument(); }); it('uses the test run title and still exposes the full trigger tab set', () => { diff --git a/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_modal.tsx b/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_modal.tsx index 7431725358944..4fd73919fab1c 100644 --- a/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_modal.tsx +++ b/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_modal.tsx @@ -94,8 +94,8 @@ export const WorkflowExecuteModal = React.memo( ); const visibleTriggerTabs = useMemo( - () => getVisibleWorkflowTriggerTabs(definition, normalizedInputs), - [definition, normalizedInputs] + () => getVisibleWorkflowTriggerTabs(definition), + [definition] ); const [selectedTrigger, setSelectedTrigger] = useState(() => diff --git a/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_modal_helpers.test.ts b/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_modal_helpers.test.ts index c5dcf09ac38a9..c776c327c2dbd 100644 --- a/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_modal_helpers.test.ts +++ b/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_modal_helpers.test.ts @@ -142,7 +142,7 @@ describe('buildDefaultTriggerEventSearchQuery', () => { describe('getVisibleWorkflowTriggerTabs', () => { it('returns all tabs when the workflow has no triggers', () => { - expect(getVisibleWorkflowTriggerTabs(null, undefined)).toEqual([ + expect(getVisibleWorkflowTriggerTabs(null)).toEqual([ 'alert', 'index', 'event', @@ -151,43 +151,22 @@ describe('getVisibleWorkflowTriggerTabs', () => { ]); }); - it('returns alert and historical for alert-only workflows', () => { + it('returns alert, manual, and historical for alert-only workflows', () => { expect( - getVisibleWorkflowTriggerTabs({ ...baseDefinition, triggers: [{ type: 'alert' }] }, undefined) - ).toEqual(['alert', 'historical']); + getVisibleWorkflowTriggerTabs({ ...baseDefinition, triggers: [{ type: 'alert' }] }) + ).toEqual(['alert', 'manual', 'historical']); }); - it('returns document and historical for manual-only workflows without inputs', () => { + it('returns document, manual, and historical for manual-only workflows', () => { expect( - getVisibleWorkflowTriggerTabs( - { ...baseDefinition, triggers: [{ type: 'manual' }] }, - undefined - ) - ).toEqual(['index', 'historical']); - }); - - it('returns manual, document, and historical when manual trigger defines inputs', () => { - const normalizedWithOneField = normalizeFieldsToJsonSchema([ - { name: 'x', type: 'string', required: true }, - ]); - expect( - getVisibleWorkflowTriggerTabs( - { - ...baseDefinition, - triggers: [{ type: 'manual', inputs: [{ name: 'x', type: 'string', required: true }] }], - } as WorkflowYaml, - normalizedWithOneField - ) + getVisibleWorkflowTriggerTabs({ ...baseDefinition, triggers: [{ type: 'manual' }] }) ).toEqual(['index', 'manual', 'historical']); }); - it('returns event and historical for custom event-driven workflows', () => { + it('returns event, manual, and historical for custom event-driven workflows', () => { expect( - getVisibleWorkflowTriggerTabs( - workflowWithExtensionTriggers([{ type: 'cases.created' }]), - undefined - ) - ).toEqual(['event', 'historical']); + getVisibleWorkflowTriggerTabs(workflowWithExtensionTriggers([{ type: 'cases.created' }])) + ).toEqual(['event', 'manual', 'historical']); }); }); diff --git a/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_modal_helpers.ts b/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_modal_helpers.ts index 57bd933ab5628..1530aa2cf296e 100644 --- a/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_modal_helpers.ts +++ b/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_modal_helpers.ts @@ -62,8 +62,7 @@ export function workflowDefinitionHasTriggerType( /** Run-modal tabs to show based on triggers declared in the workflow definition. */ export function getVisibleWorkflowTriggerTabs( - definition: WorkflowYaml | null, - normalizedInputs: NormalizedWorkflowInputs | undefined + definition: WorkflowYaml | null ): readonly WorkflowTriggerTab[] { if (!definition?.triggers?.length) { return ENABLED_TRIGGER_TABS; @@ -80,10 +79,7 @@ export function getVisibleWorkflowTriggerTabs( if (workflowDefinitionHasTriggerType(definition, 'manual')) { visible.push('index'); } - if (hasWorkflowInputFields(normalizedInputs)) { - visible.push('manual'); - } - visible.push('historical'); + visible.push('manual', 'historical'); return visible; } @@ -193,7 +189,7 @@ export function resolveInitialSelectedTrigger( canReadWorkflowExecution: boolean, normalizedInputs: NormalizedWorkflowInputs | undefined ): WorkflowTriggerTab { - const visibleTabs = getVisibleWorkflowTriggerTabs(definition, normalizedInputs); + const visibleTabs = getVisibleWorkflowTriggerTabs(definition); let selected: WorkflowTriggerTab; diff --git a/src/platform/plugins/shared/workflows_management/test/scout_workflows_ui/ui/fixtures/workflows/console_workflows.ts b/src/platform/plugins/shared/workflows_management/test/scout_workflows_ui/ui/fixtures/workflows/console_workflows.ts index e7835c95be78b..19fa63cc16c35 100644 --- a/src/platform/plugins/shared/workflows_management/test/scout_workflows_ui/ui/fixtures/workflows/console_workflows.ts +++ b/src/platform/plugins/shared/workflows_management/test/scout_workflows_ui/ui/fixtures/workflows/console_workflows.ts @@ -53,6 +53,22 @@ steps: message: "Test run: {{ execution.isTestRun }}" `; +/** + * Workflow with an event-driven trigger so the Run/Test modal shows the Event tab. + */ +export const getTestRunEventTabWorkflowYaml = (name: string) => ` +name: ${name} +enabled: false +description: Workflow with workflows.failed trigger for Event tab scout test +triggers: + - type: workflows.failed +steps: + - name: hello_world_step + type: console + with: + message: "Test run: {{ execution.isTestRun }}" +`; + /** * Workflow with a foreach loop (4 items) and a nested console step. * Used for individual step run and context override tests. diff --git a/src/platform/plugins/shared/workflows_management/test/scout_workflows_ui/ui/fixtures/workflows/index.ts b/src/platform/plugins/shared/workflows_management/test/scout_workflows_ui/ui/fixtures/workflows/index.ts index 2008bd0878fe4..306f819cde56d 100644 --- a/src/platform/plugins/shared/workflows_management/test/scout_workflows_ui/ui/fixtures/workflows/index.ts +++ b/src/platform/plugins/shared/workflows_management/test/scout_workflows_ui/ui/fixtures/workflows/index.ts @@ -12,6 +12,7 @@ export { getCreateGetUpdateCaseWorkflowYaml } from './create_get_update_case'; export { getListTestWorkflowYaml, getTestRunWorkflowYaml, + getTestRunEventTabWorkflowYaml, getWorkflowWithLoopYaml, getIterationLoopWorkflowYaml, getManyIterationsWorkflowYaml, diff --git a/src/platform/plugins/shared/workflows_management/test/scout_workflows_ui/ui/parallel_tests/workflow_execution/test_run.spec.ts b/src/platform/plugins/shared/workflows_management/test/scout_workflows_ui/ui/parallel_tests/workflow_execution/test_run.spec.ts index 3afe2ef2a80ce..c2702e5caa75b 100644 --- a/src/platform/plugins/shared/workflows_management/test/scout_workflows_ui/ui/parallel_tests/workflow_execution/test_run.spec.ts +++ b/src/platform/plugins/shared/workflows_management/test/scout_workflows_ui/ui/parallel_tests/workflow_execution/test_run.spec.ts @@ -12,7 +12,11 @@ import { expect } from '@kbn/scout/ui'; import { spaceTest as test } from '../../fixtures'; import { cleanupWorkflowsAndRules } from '../../fixtures/cleanup'; import { EXECUTION_TIMEOUT } from '../../fixtures/constants'; -import { getTestRunWorkflowYaml, getWorkflowWithLoopYaml } from '../../fixtures/workflows'; +import { + getTestRunEventTabWorkflowYaml, + getTestRunWorkflowYaml, + getWorkflowWithLoopYaml, +} from '../../fixtures/workflows'; import { getWorkflowWithEventInputYaml } from '../../fixtures/workflows/console_workflows'; test.describe('Workflow execution - Test runs', { tag: [...tags.stateful.classic] }, () => { @@ -56,7 +60,9 @@ test.describe('Workflow execution - Test runs', { tag: [...tags.stateful.classic const workflowName = 'Test Workflow Event Tab From Editor'; await pageObjects.workflowEditor.gotoNewWorkflow(); - await pageObjects.workflowEditor.setYamlEditorValue(getTestRunWorkflowYaml(workflowName)); + await pageObjects.workflowEditor.setYamlEditorValue( + getTestRunEventTabWorkflowYaml(workflowName) + ); await pageObjects.workflowEditor.saveWorkflow(); await pageObjects.workflowEditor.clickRunButton();