From 060eb8e20301b4e6ba63bf0ce15272de3fa1f0ff Mon Sep 17 00:00:00 2001 From: Yngrid Coello Date: Fri, 29 May 2026 23:36:28 +0200 Subject: [PATCH] Handle empty event tab --- .../ui/workflow_execute_event_form.test.tsx | 36 +++++++ .../ui/workflow_execute_event_form.tsx | 24 ++++- ...orkflow_execute_event_form_empty_state.tsx | 87 +++++++++++++++++ ...flow_execute_event_form_search_results.tsx | 96 +++++++++++-------- .../ui/workflow_execute_modal.tsx | 1 + .../ui/workflow_execute_modal_helpers.test.ts | 17 ++++ .../ui/workflow_execute_modal_helpers.ts | 15 +++ 7 files changed, 234 insertions(+), 42 deletions(-) create mode 100644 src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_event_form_empty_state.tsx diff --git a/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_event_form.test.tsx b/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_event_form.test.tsx index 75422ba1a91dd..54ed4f9f791a0 100644 --- a/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_event_form.test.tsx +++ b/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_event_form.test.tsx @@ -311,6 +311,42 @@ describe('WorkflowExecuteEventForm', () => { }); }); + it('shows an empty state when no trigger events match the default scope', async () => { + mockUseQueryTriggerEvents.mockReturnValue({ + data: { + hits: [], + total: 0, + page: 1, + size: 50, + }, + isLoading: false, + isFetching: false, + isPreviousData: false, + isError: false, + error: undefined, + isSuccess: true, + status: 'success', + refetch: jest.fn().mockResolvedValue(undefined), + } as unknown as ReturnType); + + const { findByTestId, getByText } = render( + + + + ); + + expect(await findByTestId('workflowTriggerEventsEmptyState')).toBeInTheDocument(); + expect(getByText('No events in the selected time range')).toBeInTheDocument(); + expect( + getByText(/widening the time range or adjusting the query in the search bar/) + ).toBeInTheDocument(); + }); + it('shows a privilege message when trigger event search returns 403', async () => { mockUseQueryTriggerEvents.mockReturnValue({ data: undefined, diff --git a/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_event_form.tsx b/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_event_form.tsx index ab569595e3010..a892eb58c3411 100644 --- a/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_event_form.tsx +++ b/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_event_form.tsx @@ -11,13 +11,17 @@ import { EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiText, useEuiTheme } from '@el import { css } from '@emotion/react'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { i18n } from '@kbn/i18n'; +import { DataLoadingState } from '@kbn/unified-data-table'; import type { WorkflowYaml } from '@kbn/workflows'; import { TIMEPICKER_FALLBACK } from './constants'; import { useTriggerEventSearch } from './use_trigger_event_search'; import { useTriggerEventTableConfig } from './use_trigger_event_table_config'; import { useWorkflowsEventsDataView } from './use_workflows_events_data_view'; import { WorkflowExecuteEventFormSearchResults } from './workflow_execute_event_form_search_results'; -import { getWorkflowCustomTriggerTypeIds } from './workflow_execute_modal_helpers'; +import { + getWorkflowCustomTriggerTypeIds, + isDefaultTriggerEventSearchScope, +} from './workflow_execute_modal_helpers'; import { useKibana } from '../../../hooks/use_kibana'; import { useSpaceId } from '../../../hooks/use_space_id'; import { useEventDrivenExecutionStatus } from '../../workflow_list/ui/use_event_driven_execution_status'; @@ -35,6 +39,8 @@ export interface WorkflowExecuteEventFormProps { onTriggerEventTableSelectionCountChange?: (selectedCount: number) => void; /** Notifies the modal when UnifiedDataTable / EuiDataGrid fullscreen toggles. */ onEventGridFullScreenChange?: (isFullScreen: boolean) => void; + /** Switches the execute modal to the Manual tab from the empty-state action. */ + onOpenManualTab?: () => void; } export const WorkflowExecuteEventForm = ({ @@ -45,6 +51,7 @@ export const WorkflowExecuteEventForm = ({ setErrors, onTriggerEventTableSelectionCountChange, onEventGridFullScreenChange, + onOpenManualTab, }: WorkflowExecuteEventFormProps): React.JSX.Element => { const { euiTheme } = useEuiTheme(); const tableSurfaceColor = euiTheme.colors.backgroundBasePlain; @@ -92,6 +99,7 @@ export const WorkflowExecuteEventForm = ({ const { query, + submittedQuery, timeRange, searchResult, isError, @@ -120,6 +128,17 @@ export const WorkflowExecuteEventForm = ({ const documentCount = searchResult?.total ?? 0; + const isDefaultTriggerScope = useMemo( + () => isDefaultTriggerEventSearchScope(submittedQuery, customTriggerTypeIds), + [submittedQuery, customTriggerTypeIds] + ); + + const showNoEventsEmptyState = + tableLoadingState === DataLoadingState.loaded && + !isError && + documentCount === 0 && + Boolean(dataView); + const tableConfig = useTriggerEventTableConfig({ services, dataView, @@ -221,6 +240,9 @@ export const WorkflowExecuteEventForm = ({ totalHits={totalHits} onFetchMoreRecords={onFetchMoreRecords} onDataGridFullScreenChange={handleDataGridFullScreenChange} + showNoEventsEmptyState={showNoEventsEmptyState} + isDefaultTriggerScope={isDefaultTriggerScope} + onOpenManualTab={onOpenManualTab} /> ); diff --git a/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_event_form_empty_state.tsx b/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_event_form_empty_state.tsx new file mode 100644 index 0000000000000..c1061185babb1 --- /dev/null +++ b/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_event_form_empty_state.tsx @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { EuiButton, EuiEmptyPrompt, EuiText } from '@elastic/eui'; +import { css } from '@emotion/react'; +import React, { memo } from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; + +export interface WorkflowExecuteEventFormEmptyStateProps { + isDefaultTriggerScope: boolean; + onOpenManualTab?: () => void; +} + +export const WorkflowExecuteEventFormEmptyState = memo(function WorkflowExecuteEventFormEmptyState({ + isDefaultTriggerScope, + onOpenManualTab, +}: WorkflowExecuteEventFormEmptyStateProps): React.JSX.Element { + const title = isDefaultTriggerScope ? ( + + ) : ( + + ); + + const body = isDefaultTriggerScope ? ( + +

+ +

+
+ ) : ( + +

+ +

+
+ ); + + return ( + {title}} + body={body} + actions={ + onOpenManualTab ? ( + + + + ) : undefined + } + css={css` + flex: 1; + min-height: 0; + justify-content: center; + `} + paddingSize="m" + titleSize="xs" + /> + ); +}); + +WorkflowExecuteEventFormEmptyState.displayName = 'WorkflowExecuteEventFormEmptyState'; diff --git a/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_event_form_search_results.tsx b/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_event_form_search_results.tsx index 22f6e86535e91..11b8e5682ef1d 100644 --- a/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_event_form_search_results.tsx +++ b/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_event_form_search_results.tsx @@ -31,6 +31,7 @@ import { UnifiedDataTable, type UnifiedDataTableRenderCustomToolbar, } from '@kbn/unified-data-table'; +import { WorkflowExecuteEventFormEmptyState } from './workflow_execute_event_form_empty_state'; import { formatTriggerEventQueryError } from './workflow_execute_event_query_errors'; export interface WorkflowExecuteEventFormSearchResultsProps { @@ -60,6 +61,9 @@ export interface WorkflowExecuteEventFormSearchResultsProps { totalHits: number; onFetchMoreRecords: (() => void) | undefined; onDataGridFullScreenChange: (isFullScreen: boolean) => void; + showNoEventsEmptyState: boolean; + isDefaultTriggerScope: boolean; + onOpenManualTab?: () => void; } export const WorkflowExecuteEventFormSearchResults = memo( @@ -90,6 +94,9 @@ export const WorkflowExecuteEventFormSearchResults = memo( totalHits, onFetchMoreRecords, onDataGridFullScreenChange, + showNoEventsEmptyState, + isDefaultTriggerScope, + onOpenManualTab, }: WorkflowExecuteEventFormSearchResultsProps): React.JSX.Element { return ( <> @@ -213,47 +220,54 @@ export const WorkflowExecuteEventFormSearchResults = memo( )} {dataView ? ( <> -
- - - -
+ {showNoEventsEmptyState ? ( + + ) : ( +
+ + + +
+ )} ) : null} 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 1c5ab03eca91b..f5d9909ecc85f 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 @@ -536,6 +536,7 @@ export const WorkflowExecuteModal = React.memo( handleEventTriggerTableSelectionCountChange } onEventGridFullScreenChange={setIsEventGridFullScreen} + onOpenManualTab={() => handleChangeTrigger('manual')} /> )} {selectedTrigger === 'historical' && ( 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..7aae8b81f7ac0 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 @@ -16,6 +16,7 @@ import { getFallbackTriggerTab, getWorkflowCustomTriggerTypeIds, hasCustomEventTrigger, + isDefaultTriggerEventSearchScope, resolveInitialSelectedTrigger, } from './workflow_execute_modal_helpers'; @@ -123,6 +124,22 @@ describe('buildWorkflowTriggerScopeKql', () => { }); }); +describe('isDefaultTriggerEventSearchScope', () => { + it('returns true for the default workflow trigger scope query', () => { + const defaultQuery = buildDefaultTriggerEventSearchQuery(['custom.trigger']); + expect(isDefaultTriggerEventSearchScope(defaultQuery, ['custom.trigger'])).toBe(true); + }); + + it('returns false when the user changes the KQL query', () => { + const defaultQuery = buildDefaultTriggerEventSearchQuery(['custom.trigger']); + expect( + isDefaultTriggerEventSearchScope({ ...defaultQuery, query: 'eventId: abc' }, [ + 'custom.trigger', + ]) + ).toBe(false); + }); +}); + describe('buildDefaultTriggerEventSearchQuery', () => { it('seeds the KQL bar with workflow trigger scope', () => { expect(buildDefaultTriggerEventSearchQuery(['custom.trigger'])).toEqual({ 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..67d112e7a2f4f 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 @@ -113,6 +113,21 @@ export function buildDefaultTriggerEventSearchQuery(workflowTriggerIds: readonly return { query: scopeKql ?? '', language: 'kuery' }; } +function normalizeTriggerEventSearchKql(query: Query): string { + return typeof query.query === 'string' ? query.query.trim() : ''; +} + +/** True when the submitted KQL matches the workflow's default trigger scope (not user-filtered). */ +export function isDefaultTriggerEventSearchScope( + submittedQuery: Query, + workflowTriggerIds: readonly string[] +): boolean { + return ( + normalizeTriggerEventSearchKql(submittedQuery) === + normalizeTriggerEventSearchKql(buildDefaultTriggerEventSearchQuery(workflowTriggerIds)) + ); +} + export function getDefaultTrigger(definition: WorkflowYaml | null): WorkflowTriggerTab { if (!definition) { return 'alert';