Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -1048,6 +1048,9 @@ export class WorkflowsExecutionEnginePlugin
spaceId,
fakeRequest: request,
});

// Same idea as cancel: nudge TM so the resume task runs as soon as possible
await workflowTaskManager.forceRunIdleTasks(executionId);
Comment thread
h88 marked this conversation as resolved.
};

const workflowEventLoggerService = new WorkflowEventLoggerService(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,5 +261,28 @@ describe('ResumeExecutionButton', () => {
expect(screen.getByTestId('provideActionButton')).not.toBeDisabled();
});
});

it('re-enables the button when pausedWaitStepExecutionId changes after a successful submit', async () => {
const { rerender } = render(
<TestWrapper>
<ResumeExecutionButton {...defaultProps} pausedWaitStepExecutionId="wait-step-exec-1" />
</TestWrapper>
);
fireEvent.click(screen.getByTestId('provideActionButton'));
await waitFor(() => expect(capturedOnSubmit).toBeDefined());
act(() => {
capturedOnSubmit!({ stepInputs: { approved: true } });
});
await waitFor(() => {
expect(screen.getByTestId('provideActionButton')).toBeDisabled();
});

rerender(
<TestWrapper>
<ResumeExecutionButton {...defaultProps} pausedWaitStepExecutionId="wait-step-exec-2" />
</TestWrapper>
);
expect(screen.getByTestId('provideActionButton')).not.toBeDisabled();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,16 @@ interface ResumeExecutionButtonProps {
resumeSchema?: JsonModelSchemaType;
/** When true, opens the input modal immediately on mount */
autoOpen?: boolean;
/** Step execution id of the current waitForInput pause; when it changes re-enable after prior submit */
pausedWaitStepExecutionId?: string;
}

export const ResumeExecutionButton: React.FC<ResumeExecutionButtonProps> = ({
executionId,
resumeMessage,
resumeSchema,
autoOpen = false,
pausedWaitStepExecutionId,
}) => {
const { notifications } = useKibana().services;
const workflowsApi = useWorkflowsApi();
Expand All @@ -49,6 +52,10 @@ export const ResumeExecutionButton: React.FC<ResumeExecutionButtonProps> = ({
if (autoOpen) setIsModalOpen(true);
}, [autoOpen]);

useEffect(() => {
setIsSubmitted(false);
}, [pausedWaitStepExecutionId]);

const contextOverride = useMemo<ContextOverrideData | undefined>(() => {
if (!resumeSchema) return undefined;
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ export const WorkflowExecutionDetail: React.FC<WorkflowExecutionDetailProps> = R
resumeMessage={resumeMessage}
resumeSchema={resumeSchema}
shouldAutoResume={shouldAutoResume}
pausedWaitStepExecutionId={pausedStepId}
childWorkflowExecution={selectedStepChildExecution}
parentWorkflowExecution={parentWorkflowExecution}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ interface WorkflowExecutionOverviewProps {
resumeMessage?: string;
resumeSchema?: JsonModelSchemaType;
shouldAutoResume?: boolean;
pausedWaitStepExecutionId?: string;
}

const formatExecutionDate = (date: string) => {
Expand Down Expand Up @@ -60,6 +61,7 @@ export const WorkflowExecutionOverview = React.memo<WorkflowExecutionOverviewPro
resumeMessage,
resumeSchema,
shouldAutoResume = false,
pausedWaitStepExecutionId,
}) => {
const { euiTheme } = useEuiTheme();

Expand Down Expand Up @@ -199,6 +201,7 @@ export const WorkflowExecutionOverview = React.memo<WorkflowExecutionOverviewPro
resumeMessage={resumeMessage}
resumeSchema={resumeSchema}
autoOpen={shouldAutoResume}
pausedWaitStepExecutionId={pausedWaitStepExecutionId}
/>
</EuiFlexItem>
)}
Expand Down
Comment thread
macroscopeapp[bot] marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ interface WorkflowStepExecutionDetailsProps {
resumeMessage?: string;
resumeSchema?: JsonModelSchemaType;
shouldAutoResume?: boolean;
pausedWaitStepExecutionId?: string;
/** When the step is workflow.execute, the child workflow execution (to link to) */
childWorkflowExecution?: ChildWorkflowExecutionItem;
/** When viewing a step that belongs to a nested execution, the parent workflow execution (to link to) */
Expand All @@ -59,6 +60,7 @@ export const WorkflowStepExecutionDetails = React.memo<WorkflowStepExecutionDeta
resumeMessage,
resumeSchema,
shouldAutoResume = false,
pausedWaitStepExecutionId,
childWorkflowExecution,
parentWorkflowExecution,
}) => {
Expand Down Expand Up @@ -187,6 +189,7 @@ export const WorkflowStepExecutionDetails = React.memo<WorkflowStepExecutionDeta
resumeMessage={resumeMessage}
resumeSchema={resumeSchema}
shouldAutoResume={shouldAutoResume}
pausedWaitStepExecutionId={pausedWaitStepExecutionId}
/>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ describe('getExecutionState', () => {

expect(state?.status).toBe(ExecutionStatus.WAITING_FOR_INPUT);
expect(state?.waiting_input).toEqual({
waiting_step_id: 'approve',
message: 'Please approve this request',
schema: {
type: 'object',
Expand Down Expand Up @@ -207,6 +208,7 @@ describe('getExecutionState', () => {
});

expect(state?.waiting_input).toEqual({
waiting_step_id: 'ask',
message: 'Provide input',
});
});
Expand Down Expand Up @@ -245,7 +247,10 @@ describe('getExecutionState', () => {
workflowApi,
});

expect(state?.waiting_input?.message).toBe('Approve item');
expect(state?.waiting_input).toEqual({
waiting_step_id: 'nested_ask',
message: 'Approve item',
});
});

it('omits waiting_input entirely when no waiting step is found', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { getWorkflowOutput } from './get_workflow_output';
type WorkflowApi = WorkflowsServerPluginSetup['management'];

export interface WaitingInputContext {
/** `waitForInput` step id currently blocking (from step execution). Compare across status polls. */
waiting_step_id: string;
/** Human-readable prompt from the waitForInput step's `with.message`. */
message?: string;
/** JSON Schema describing the expected input, from the step's `with.schema`. */
Expand All @@ -30,7 +32,7 @@ export interface WorkflowExecutionState {
workflow_name?: string;
/** Present when status is FAILED; contains the workflow error message. */
error_message?: string;
/** Present when status is WAITING_FOR_INPUT; describes what input is needed to resume. */
/** Present when status is WAITING_FOR_INPUT. */
waiting_input?: WaitingInputContext;
}

Expand Down Expand Up @@ -83,12 +85,11 @@ export const getExecutionState = async ({
const stepConfig =
step?.type === 'waitForInput' ? (step as WaitForInputStep).with : undefined;
const waitContext: WaitingInputContext = {
waiting_step_id: waitingStep.stepId,
...(stepConfig?.message && { message: stepConfig.message }),
...(stepConfig?.schema && { schema: stepConfig.schema as Record<string, unknown> }),
};
if (waitContext.message !== undefined || waitContext.schema !== undefined) {
state.waiting_input = waitContext;
}
state.waiting_input = waitContext;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ export const getWorkflowToolType = ({
- If the workflow wasn't completed, a workflow execution ID will be returned.
- The ${platformCoreTools.getWorkflowExecutionStatus} tool can be used later to check the status of the workflow execution.
- If the workflow returns with status "waiting_for_input", it is paused and requires human input to continue.
The response will include a "waiting_input" object with a "message" (what the workflow is asking for)
and an optional "schema" (JSON Schema describing the expected input fields).
The response will include a "waiting_input" object with "waiting_step_id" (which waitForInput step is active),
a "message" (what the workflow is asking for), and an optional "schema" (JSON Schema describing the expected input fields).
Use the ${platformCoreTools.resumeWorkflowExecution} tool to provide the required input and resume the workflow.

`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,14 @@ export const resumeWorkflowExecutionTool = ({

Use this tool when a workflow execution returned with status "waiting_for_input".
The execution's "waiting_input" field describes what input is expected (message and optional schema).

Provide the executionId and the required input object to resume the workflow.
After resuming, use ${platformCoreTools.getWorkflowExecutionStatus} to check whether the workflow completed or paused again (e.g. inside a loop).

**Important — read after resume:** a successful call means resume was **accepted and scheduled** in the engine.
The execution record can **lag**: you may still briefly see the same "waiting_for_input" and the same form right after this tool returns. **Do not** tell the user there is another HITL step, a loop, or a second approval round unless you have **confirmed** a genuinely new pause (clear evidence such as a new step, changed context, or repeated status checks over time that rule out a stale snapshot).

**What you should do:** call ${platformCoreTools.getWorkflowExecutionStatus} on the same executionId. Compare **waiting_input.waiting_step_id** between polls: if it **changes** while still waiting_for_input, that is evidence of a **new** HITL step; if it is the **same** id immediately after resume, treat it as likely stale until status moves on. If the status still looks unchanged, wait a few seconds and check again. Repeat for up to **about 90 seconds** total, or until the status clearly updates (e.g. completed, failed, running).

**If after ~90 seconds you still cannot confirm an update:** tell the user their resume was **submitted**, but you **could not confirm** the new execution state from Kibana yet — do **not** invent a second approval workflow.
`),
schema: resumeWorkflowExecutionSchema,
handler: async ({ executionId, input }, { spaceId, request }) => {
Expand Down
Loading