Skip to content
Open
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 @@ -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<typeof useQueryTriggerEvents>);

const { findByTestId, getByText } = render(
<TestWrapper>
<WorkflowExecuteEventForm
definition={baseDefinition as any}
value=""
setValue={mockSetValue}
errors={null}
/>
</TestWrapper>
);

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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 = ({
Expand All @@ -45,6 +51,7 @@ export const WorkflowExecuteEventForm = ({
setErrors,
onTriggerEventTableSelectionCountChange,
onEventGridFullScreenChange,
onOpenManualTab,
}: WorkflowExecuteEventFormProps): React.JSX.Element => {
const { euiTheme } = useEuiTheme();
const tableSurfaceColor = euiTheme.colors.backgroundBasePlain;
Expand Down Expand Up @@ -92,6 +99,7 @@ export const WorkflowExecuteEventForm = ({

const {
query,
submittedQuery,
timeRange,
searchResult,
isError,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -221,6 +240,9 @@ export const WorkflowExecuteEventForm = ({
totalHits={totalHits}
onFetchMoreRecords={onFetchMoreRecords}
onDataGridFullScreenChange={handleDataGridFullScreenChange}
showNoEventsEmptyState={showNoEventsEmptyState}
isDefaultTriggerScope={isDefaultTriggerScope}
onOpenManualTab={onOpenManualTab}
/>
</EuiFlexGroup>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -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 ? (
<FormattedMessage
id="workflows.workflowExecuteEventTriggerForm.noEventsTitle"
defaultMessage="No events in the selected time range"
/>
) : (
<FormattedMessage
id="workflows.workflowExecuteEventTriggerForm.noMatchingEventsTitle"
defaultMessage="No events match your search"
/>
);

const body = isDefaultTriggerScope ? (
<EuiText size="s" textAlign="center">
<p>
<FormattedMessage
id="workflows.workflowExecuteEventTriggerForm.noEventsBody"
defaultMessage="No events match this workflow's trigger scope in the selected time range. Try widening the time range or adjusting the query in the search bar, use the Manual tab to run with custom JSON, or trigger a real event and refresh."
/>
</p>
</EuiText>
) : (
<EuiText size="s" textAlign="center">
<p>
<FormattedMessage
id="workflows.workflowExecuteEventTriggerForm.noMatchingEventsBody"
defaultMessage="Try widening the time range, adjusting the query in the search bar, or use the Manual tab to provide run input as JSON."
/>
</p>
</EuiText>
);

return (
<EuiEmptyPrompt
data-test-subj="workflowTriggerEventsEmptyState"
iconType="calendar"
title={<h3>{title}</h3>}
body={body}
actions={
onOpenManualTab ? (
<EuiButton
size="s"
onClick={onOpenManualTab}
data-test-subj="workflowTriggerEventsOpenManualTab"
>
<FormattedMessage
id="workflows.workflowExecuteEventTriggerForm.openManualTab"
defaultMessage="Open Manual tab"
/>
</EuiButton>
) : undefined
}
css={css`
flex: 1;
min-height: 0;
justify-content: center;
`}
paddingSize="m"
titleSize="xs"
/>
);
});

WorkflowExecuteEventFormEmptyState.displayName = 'WorkflowExecuteEventFormEmptyState';
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -90,6 +94,9 @@ export const WorkflowExecuteEventFormSearchResults = memo(
totalHits,
onFetchMoreRecords,
onDataGridFullScreenChange,
showNoEventsEmptyState,
isDefaultTriggerScope,
onOpenManualTab,
}: WorkflowExecuteEventFormSearchResultsProps): React.JSX.Element {
return (
<>
Expand Down Expand Up @@ -213,47 +220,54 @@ export const WorkflowExecuteEventFormSearchResults = memo(
)}
{dataView ? (
<>
<div
css={css({
flex: 1,
minHeight: 0,
display: 'flex',
flexDirection: 'column',
})}
>
<CellActionsProvider getTriggerCompatibleActions={getNoCellActions}>
<UnifiedDataTable
ariaLabelledBy="workflowTriggerEventsTable"
columns={visibleTableColumns}
columnsMeta={columnsMeta}
rows={dataTableRows}
dataView={dataView}
loadingState={tableLoadingState}
sampleSizeState={rowsLength}
services={unifiedDataTableServices}
onSetColumns={handleUnifiedDataTableSetColumns}
showTimeCol={showTimeColumn}
sort={sort}
onSort={handleSortChange}
isSortEnabled={true}
isPaginationEnabled={true}
paginationMode="infinite"
totalHits={totalHits}
onFetchMoreRecords={onFetchMoreRecords}
dataGridDensityState={DataGridDensity.NORMAL}
isPlainRecord={true}
showFullScreenButton={true}
showKeyboardShortcuts={false}
enableInTableSearch={true}
controlColumnIds={[SELECT_ROW]}
customGridColumnsConfiguration={customGridColumnsConfiguration}
renderCustomToolbar={renderCustomToolbar}
renderCellPopover={renderCellPopover}
externalCustomRenderers={externalCustomRenderers}
onFullScreenChange={onDataGridFullScreenChange}
/>
</CellActionsProvider>
</div>
{showNoEventsEmptyState ? (
<WorkflowExecuteEventFormEmptyState
isDefaultTriggerScope={isDefaultTriggerScope}
onOpenManualTab={onOpenManualTab}
/>
) : (
<div
css={css({
flex: 1,
minHeight: 0,
display: 'flex',
flexDirection: 'column',
})}
>
<CellActionsProvider getTriggerCompatibleActions={getNoCellActions}>
<UnifiedDataTable
ariaLabelledBy="workflowTriggerEventsTable"
columns={visibleTableColumns}
columnsMeta={columnsMeta}
rows={dataTableRows}
dataView={dataView}
loadingState={tableLoadingState}
sampleSizeState={rowsLength}
services={unifiedDataTableServices}
onSetColumns={handleUnifiedDataTableSetColumns}
showTimeCol={showTimeColumn}
sort={sort}
onSort={handleSortChange}
isSortEnabled={true}
isPaginationEnabled={true}
paginationMode="infinite"
totalHits={totalHits}
onFetchMoreRecords={onFetchMoreRecords}
dataGridDensityState={DataGridDensity.NORMAL}
isPlainRecord={true}
showFullScreenButton={true}
showKeyboardShortcuts={false}
enableInTableSearch={true}
controlColumnIds={[SELECT_ROW]}
customGridColumnsConfiguration={customGridColumnsConfiguration}
renderCustomToolbar={renderCustomToolbar}
renderCellPopover={renderCellPopover}
externalCustomRenderers={externalCustomRenderers}
onFullScreenChange={onDataGridFullScreenChange}
/>
</CellActionsProvider>
</div>
)}
</>
) : null}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,7 @@ export const WorkflowExecuteModal = React.memo<WorkflowExecuteModalProps>(
handleEventTriggerTableSelectionCountChange
}
onEventGridFullScreenChange={setIsEventGridFullScreen}
onOpenManualTab={() => handleChangeTrigger('manual')}
/>
)}
{selectedTrigger === 'historical' && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
getFallbackTriggerTab,
getWorkflowCustomTriggerTypeIds,
hasCustomEventTrigger,
isDefaultTriggerEventSearchScope,
resolveInitialSelectedTrigger,
} from './workflow_execute_modal_helpers';

Expand Down Expand Up @@ -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({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
Loading