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..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 @@ -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('Manual')).toBeInTheDocument(); + expect(getByText('Historical')).toBeInTheDocument(); + expect(queryByText('Document')).not.toBeInTheDocument(); + expect(queryByText('Event')).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), + [definition] + ); + 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..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 @@ -14,6 +14,7 @@ import { buildDefaultTriggerEventSearchQuery, buildWorkflowTriggerScopeKql, getFallbackTriggerTab, + getVisibleWorkflowTriggerTabs, getWorkflowCustomTriggerTypeIds, hasCustomEventTrigger, resolveInitialSelectedTrigger, @@ -139,6 +140,36 @@ describe('buildDefaultTriggerEventSearchQuery', () => { }); }); +describe('getVisibleWorkflowTriggerTabs', () => { + it('returns all tabs when the workflow has no triggers', () => { + expect(getVisibleWorkflowTriggerTabs(null)).toEqual([ + 'alert', + 'index', + 'event', + 'manual', + 'historical', + ]); + }); + + it('returns alert, manual, and historical for alert-only workflows', () => { + expect( + getVisibleWorkflowTriggerTabs({ ...baseDefinition, triggers: [{ type: 'alert' }] }) + ).toEqual(['alert', 'manual', 'historical']); + }); + + it('returns document, manual, and historical for manual-only workflows', () => { + expect( + getVisibleWorkflowTriggerTabs({ ...baseDefinition, triggers: [{ type: 'manual' }] }) + ).toEqual(['index', 'manual', 'historical']); + }); + + it('returns event, manual, and historical for custom event-driven workflows', () => { + expect( + getVisibleWorkflowTriggerTabs(workflowWithExtensionTriggers([{ type: 'cases.created' }])) + ).toEqual(['event', 'manual', 'historical']); + }); +}); + describe('getFallbackTriggerTab', () => { const normalizedWithOneField: NormalizedWorkflowInputs = normalizeFieldsToJsonSchema([ { name: 'x', type: 'string', required: true }, @@ -174,9 +205,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..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 @@ -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,47 @@ 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 +): 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'); + } + visible.push('manual', '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 +189,36 @@ export function resolveInitialSelectedTrigger( canReadWorkflowExecution: boolean, normalizedInputs: NormalizedWorkflowInputs | undefined ): WorkflowTriggerTab { - if (initialExecutionId) { - return canReadWorkflowExecution - ? 'historical' - : getFallbackTriggerTab(normalizedInputs, definition, canReadWorkflowExecution); - } + const visibleTabs = getVisibleWorkflowTriggerTabs(definition); - 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); } 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();