diff --git a/src/platform/plugins/shared/workflows_management/common/translations.ts b/src/platform/plugins/shared/workflows_management/common/translations.ts index 09c83f62a0526..342ec29790f83 100644 --- a/src/platform/plugins/shared/workflows_management/common/translations.ts +++ b/src/platform/plugins/shared/workflows_management/common/translations.ts @@ -23,30 +23,6 @@ export const SEARCH_PLACEHOLDER = i18n.translate( } ); -export const MANUAL_TRIGGERS_DESCRIPTIONS: Record = { - manual: i18n.translate( - 'plugins.workflowsManagement.workflowsExecution.manualTriggerDescription', - { - defaultMessage: - 'Provide custom JSON data manually for testing. Ideal for simulating specific scenarios or debugging edge cases.', - } - ), - index: i18n.translate('plugins.workflowsManagement.workflowsExecution.indexTriggerDescription', { - defaultMessage: - 'Choose a document directly from an index to use as the test input. This is helpful for verifying workflows against real indexed data.', - }), - alert: i18n.translate('plugins.workflowsManagement.workflowsExecution.alertTriggerDescription', { - defaultMessage: - 'Choose an existing alert directly from an index to use as the test input. This is helpful for verifying workflows against real alerts data.', - }), - scheduled: i18n.translate( - 'plugins.workflowsManagement.workflowsExecution.scheduledTriggerDescription', - { - defaultMessage: 'Select a schedule to trigger workflow', - } - ), -}; - export const TRIGGERS_LIST_TITLE = i18n.translate( 'plugins.workflowsManagement.workflowsList.triggersListTitle', { diff --git a/src/platform/plugins/shared/workflows_management/public/common/lib/telemetry/events/workflows/execution/index.ts b/src/platform/plugins/shared/workflows_management/public/common/lib/telemetry/events/workflows/execution/index.ts index 8dc5ea7a782a5..b8d0d79ce6dc7 100644 --- a/src/platform/plugins/shared/workflows_management/public/common/lib/telemetry/events/workflows/execution/index.ts +++ b/src/platform/plugins/shared/workflows_management/public/common/lib/telemetry/events/workflows/execution/index.ts @@ -100,7 +100,8 @@ const workflowTestRunInitiatedSchema: RootSchema({ mutationKey: ['POST', 'workflows', 'id', 'run'], mutationFn: ({ id, inputs }) => { diff --git a/src/platform/plugins/shared/workflows_management/public/entities/workflows/model/use_workflow_execution.ts b/src/platform/plugins/shared/workflows_management/public/entities/workflows/model/use_workflow_execution.ts new file mode 100644 index 0000000000000..a2901db8a11f1 --- /dev/null +++ b/src/platform/plugins/shared/workflows_management/public/entities/workflows/model/use_workflow_execution.ts @@ -0,0 +1,33 @@ +/* + * 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 { useQuery } from '@kbn/react-query'; +import type { WorkflowExecutionDto } from '@kbn/workflows'; +import { useKibana } from '../../../hooks/use_kibana'; + +interface UseWorkflowExecutionParams { + executionId: string | null; + enabled?: boolean; +} + +export function useWorkflowExecution({ executionId, enabled = true }: UseWorkflowExecutionParams) { + const { http } = useKibana().services; + + return useQuery({ + queryKey: ['workflowExecution', executionId], + queryFn: async () => { + if (!executionId) return null; + const response = await http.get( + `/api/workflowExecutions/${executionId}` + ); + return response; + }, + enabled: enabled && executionId !== null, + }); +} diff --git a/src/platform/plugins/shared/workflows_management/public/entities/workflows/store/workflow_detail/selectors.ts b/src/platform/plugins/shared/workflows_management/public/entities/workflows/store/workflow_detail/selectors.ts index 47c00dc7e93b3..f980e3c86d356 100644 --- a/src/platform/plugins/shared/workflows_management/public/entities/workflows/store/workflow_detail/selectors.ts +++ b/src/platform/plugins/shared/workflows_management/public/entities/workflows/store/workflow_detail/selectors.ts @@ -52,8 +52,8 @@ export const selectWorkflowDefinition = createSelector( ); // Only checks if the current workflow yaml can be parsed, does not check the schema, only the yaml syntax -export const selectIsYamlSyntaxValid = createSelector(selectYamlComputed, (computed): boolean => - Boolean(computed?.workflowDefinition) +export const selectIsYamlSyntaxValid = createSelector(selectYamlDocument, (yamlDoc): boolean => + Boolean(yamlDoc && yamlDoc.errors.length === 0) ); // Checks whether validation errors (from strict schema + custom validations) are present @@ -74,6 +74,11 @@ export const selectIsTestModalOpen = createSelector( (detail) => detail.isTestModalOpen ); +export const selectReplayExecutionId = createSelector( + selectDetail, + (detail) => detail.replayExecutionId +); + export const selectIsSavingYaml = createSelector( selectDetail, (detail) => detail.loading.isSavingYaml diff --git a/src/platform/plugins/shared/workflows_management/public/entities/workflows/store/workflow_detail/slice.ts b/src/platform/plugins/shared/workflows_management/public/entities/workflows/store/workflow_detail/slice.ts index 88aad8972d9e8..09d21f24474ff 100644 --- a/src/platform/plugins/shared/workflows_management/public/entities/workflows/store/workflow_detail/slice.ts +++ b/src/platform/plugins/shared/workflows_management/public/entities/workflows/store/workflow_detail/slice.ts @@ -30,6 +30,7 @@ const initialState: WorkflowDetailState = { focusedStepId: undefined, highlightedStepId: undefined, isTestModalOpen: false, + replayExecutionId: null, loading: initialLoadingState, hasYamlSchemaValidationErrors: false, connectorFlyout: { @@ -76,6 +77,9 @@ const workflowDetailSlice = createSlice({ setIsTestModalOpen: (state, action: { payload: boolean }) => { state.isTestModalOpen = action.payload; }, + setReplayExecutionId: (state, action: { payload: string | null }) => { + state.replayExecutionId = action.payload; + }, setConnectors: (state, action: { payload: WorkflowDetailState['connectors'] }) => { state.connectors = action.payload; }, @@ -152,6 +156,7 @@ export const { setCursorPosition, setHighlightedStepId, setIsTestModalOpen, + setReplayExecutionId, setConnectors, setExecution, clearExecution, diff --git a/src/platform/plugins/shared/workflows_management/public/entities/workflows/store/workflow_detail/thunks/test_workflow_thunk.ts b/src/platform/plugins/shared/workflows_management/public/entities/workflows/store/workflow_detail/thunks/test_workflow_thunk.ts index 357d05b7aa6ea..b3e10224a44e2 100644 --- a/src/platform/plugins/shared/workflows_management/public/entities/workflows/store/workflow_detail/thunks/test_workflow_thunk.ts +++ b/src/platform/plugins/shared/workflows_management/public/entities/workflows/store/workflow_detail/thunks/test_workflow_thunk.ts @@ -10,13 +10,14 @@ import { createAsyncThunk } from '@reduxjs/toolkit'; import { i18n } from '@kbn/i18n'; import { WorkflowsBaseTelemetry } from '../../../../../common/service/telemetry'; +import type { WorkflowTriggerTab } from '../../../../../features/run_workflow/ui/types'; import type { WorkflowsServices } from '../../../../../types'; import type { RootState } from '../../types'; import { selectWorkflow, selectYamlString } from '../selectors'; export interface TestWorkflowParams { inputs: Record; - triggerTab?: 'manual' | 'alert' | 'index'; + triggerTab?: WorkflowTriggerTab; } export interface TestWorkflowResponse { diff --git a/src/platform/plugins/shared/workflows_management/public/entities/workflows/store/workflow_detail/types.ts b/src/platform/plugins/shared/workflows_management/public/entities/workflows/store/workflow_detail/types.ts index 7312ad9c68666..2a977b8c26caa 100644 --- a/src/platform/plugins/shared/workflows_management/public/entities/workflows/store/workflow_detail/types.ts +++ b/src/platform/plugins/shared/workflows_management/public/entities/workflows/store/workflow_detail/types.ts @@ -43,6 +43,8 @@ export interface WorkflowDetailState { highlightedStepId?: string; /** The modal to test the workflow is open */ isTestModalOpen: boolean; + /** When set, open test modal in "From historical" mode with this execution pre-selected */ + replayExecutionId: string | null; /** The connectors data */ connectors?: ConnectorsResponse; /** The schema for the workflow, depends on the connectors available */ diff --git a/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/constants.ts b/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/constants.ts new file mode 100644 index 0000000000000..6c339bc2dc497 --- /dev/null +++ b/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/constants.ts @@ -0,0 +1,10 @@ +/* + * 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". + */ + +export const ENABLED_TRIGGER_TABS = ['alert', 'index', 'manual', 'historical'] as const; diff --git a/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/translations.ts b/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/translations.ts new file mode 100644 index 0000000000000..bee938aa6b27e --- /dev/null +++ b/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/translations.ts @@ -0,0 +1,46 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import type { WorkflowTriggerTab } from './types'; + +export const TRIGGER_TABS_LABELS: Record = { + alert: i18n.translate('plugins.workflowsManagement.workflowsExecution.alertTriggerLabel', { + defaultMessage: 'Alert', + }), + index: i18n.translate('plugins.workflowsManagement.workflowsExecution.indexTriggerLabel', { + defaultMessage: 'Document', + }), + manual: i18n.translate('plugins.workflowsManagement.workflowsExecution.manualTriggerLabel', { + defaultMessage: 'Manual', + }), + historical: i18n.translate( + 'plugins.workflowsManagement.workflowsExecution.historicalTriggerLabel', + { defaultMessage: 'Historical' } + ), +}; + +export const TRIGGER_TABS_DESCRIPTIONS: Record = { + manual: i18n.translate( + 'plugins.workflowsManagement.workflowsExecution.manualTriggerDescription', + { + defaultMessage: 'Provide custom JSON data manually.', + } + ), + index: i18n.translate('plugins.workflowsManagement.workflowsExecution.indexTriggerDescription', { + defaultMessage: 'Choose a document from Elasticsearch.', + }), + alert: i18n.translate('plugins.workflowsManagement.workflowsExecution.alertTriggerDescription', { + defaultMessage: 'Choose an existing alert directly.', + }), + historical: i18n.translate( + 'plugins.workflowsManagement.workflowsExecution.historicalTriggerDescription', + { defaultMessage: 'Reuse input data from previous executions.' } + ), +}; diff --git a/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/types.ts b/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/types.ts new file mode 100644 index 0000000000000..35c1ea01709e3 --- /dev/null +++ b/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/types.ts @@ -0,0 +1,12 @@ +/* + * 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 type { ENABLED_TRIGGER_TABS } from './constants'; + +export type WorkflowTriggerTab = (typeof ENABLED_TRIGGER_TABS)[number]; 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 23255a7f6a23b..133e3fe239af9 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 @@ -15,6 +15,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, + EuiPanel, EuiSpacer, EuiText, } from '@elastic/eui'; @@ -325,29 +326,31 @@ export const WorkflowExecuteEventForm = ({ - + + + {alertsLoading ? ( diff --git a/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_historical_form.test.tsx b/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_historical_form.test.tsx new file mode 100644 index 0000000000000..d9449ca7e4c87 --- /dev/null +++ b/src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_execute_historical_form.test.tsx @@ -0,0 +1,354 @@ +/* + * 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 { fireEvent, render, screen } from '@testing-library/react'; +import React from 'react'; +import { I18nProvider } from '@kbn/i18n-react'; +import { ExecutionStatus } from '@kbn/workflows'; +import { + NOT_READY_SENTINEL, + WorkflowExecuteHistoricalForm, +} from './workflow_execute_historical_form'; + +const mockUseWorkflowExecution = jest.fn(); +jest.mock('../../../entities/workflows/model/use_workflow_execution', () => ({ + useWorkflowExecution: (...args: any[]) => mockUseWorkflowExecution(...args), +})); + +const mockUseWorkflowExecutions = jest.fn(); +jest.mock('../../../entities/workflows/model/use_workflow_executions', () => ({ + useWorkflowExecutions: (...args: any[]) => mockUseWorkflowExecutions(...args), +})); + +jest.mock('../../../shared/ui/use_formatted_date', () => ({ + useGetFormattedDateTime: () => (date: Date) => date.toISOString(), +})); + +jest.mock('@kbn/code-editor', () => ({ + CodeEditor: ({ + value, + onChange, + dataTestSubj, + }: { + value: string; + onChange: (v: string) => void; + dataTestSubj: string; + }) => ( +