Skip to content

Commit 7f3bd14

Browse files
skynetigorkibanamachinesemd
authored
[One Workflow] fix: step actions bug (#236769)
## Summary Closes: elastic/security-team#14052 https://github.com/user-attachments/assets/49389cbd-24d7-49e5-9d34-82754369dfa2 ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) - [ ] Review the [backport guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing) and apply applicable `backport:*` labels. ### Identify risks Does this PR introduce any risks? For example, consider risks like hard to test bugs, performance regression, potential of data loss. Describe the risk, its severity, and mitigation for each identified risk. Invite stakeholders and evaluate how to proceed before merging. - [ ] [See some risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) - [ ] ... --------- Co-authored-by: kibanamachine <[email protected]> Co-authored-by: Sergi Massaneda <[email protected]>
1 parent 02148cf commit 7f3bd14

28 files changed

+1325
-1692
lines changed

src/platform/packages/shared/kbn-workflows/graph/workflow_graph/workflow_graph.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* License v3.0 only", or the "Server Side Public License, v 1".
88
*/
99

10+
import type { GraphEdge } from '@dagrejs/dagre';
1011
import { graphlib } from '@dagrejs/dagre';
1112
import type { GraphNodeUnion } from '../types';
1213
import { convertToWorkflowGraph } from '../build_execution_graph/build_execution_graph';
@@ -74,6 +75,10 @@ export class WorkflowGraph {
7475
return this.graph.edges().map((edge) => ({ v: edge.v, w: edge.w }));
7576
}
7677

78+
public getEdge(edgeMetadata: { v: string; w: string }): GraphEdge {
79+
return this.graph.edge(edgeMetadata);
80+
}
81+
7782
public hasStep(stepId: string): boolean {
7883
if (!this.stepIdsSet) {
7984
this.stepIdsSet = new Set(this.getAllNodes().map((node) => node.stepId));

src/platform/plugins/shared/workflows_management/public/features/debug-graph/execution_graph.tsx

Lines changed: 8 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,18 @@
77
* License v3.0 only", or the "Server Side Public License, v 1".
88
*/
99

10-
import React, { useEffect, useMemo } from 'react';
11-
import { convertToWorkflowGraph } from '@kbn/workflows/graph';
10+
import React, { useMemo } from 'react';
1211
import type { NodeTypes, Node } from '@xyflow/react';
1312
import { Background, Controls, ReactFlow } from '@xyflow/react';
1413
import { useEuiTheme } from '@elastic/eui';
15-
import { getWorkflowZodSchemaLoose } from '../../../common/schema';
16-
import { parseWorkflowYamlToJSON } from '../../../common/lib/yaml_utils';
14+
import { useSelector } from 'react-redux';
15+
1716
import { ExecutionGraphEdge, ExecutionGraphNode } from './nodes';
1817
import { convertWorkflowGraphToReactFlow } from './workflow_graph_layout';
1918
import { mainScopeNodes, secondaryScopeNodes, atomicNodes } from './nodes/types';
2019

2120
import '@xyflow/react/dist/style.css';
21+
import { selectWorkflowGraph } from '../../widgets/workflow_yaml_editor/lib/store';
2222

2323
export interface ExecutionGraphProps {
2424
workflowYaml: string | undefined;
@@ -82,57 +82,22 @@ const ReactFlowWrapper: React.FC<{
8282

8383
export const ExecutionGraph: React.FC<ExecutionGraphProps> = ({ workflowYaml }) => {
8484
const { euiTheme } = useEuiTheme();
85-
const [debouncedWorkflowYaml, setDebouncedWorkflowYaml] = React.useState(workflowYaml);
86-
87-
useEffect(() => {
88-
const handler = setTimeout(() => {
89-
setDebouncedWorkflowYaml(workflowYaml);
90-
}, 500);
91-
return () => {
92-
clearTimeout(handler);
93-
};
94-
}, [workflowYaml]);
95-
96-
const workflowExecutionGraph: { result: any; error: any } | null = useMemo(() => {
97-
if (!debouncedWorkflowYaml) {
98-
return null;
99-
}
100-
let result = null;
101-
let error = null;
102-
try {
103-
const parsingResult = parseWorkflowYamlToJSON(
104-
debouncedWorkflowYaml,
105-
getWorkflowZodSchemaLoose()
106-
);
107-
if (parsingResult.error) {
108-
error = parsingResult.error;
109-
}
110-
result = convertToWorkflowGraph((parsingResult as { data: any }).data);
111-
} catch (e) {
112-
error = e;
113-
}
114-
115-
return { result, error };
116-
}, [debouncedWorkflowYaml]);
85+
const workflowGraph = useSelector(selectWorkflowGraph);
11786

11887
const layoutResult: { result: any; error: string } | null = useMemo(() => {
119-
if (!workflowExecutionGraph) {
88+
if (!workflowGraph) {
12089
return null;
12190
}
12291

123-
if (workflowExecutionGraph.error) {
124-
return { result: null, error: workflowExecutionGraph.error };
125-
}
126-
12792
let result = null;
12893
let error = null;
12994
try {
130-
result = convertWorkflowGraphToReactFlow(workflowExecutionGraph.result);
95+
result = convertWorkflowGraphToReactFlow(workflowGraph);
13196
} catch (e) {
13297
error = e.message;
13398
}
13499
return { result, error };
135-
}, [workflowExecutionGraph]);
100+
}, [workflowGraph]);
136101

137102
return (
138103
<>

src/platform/plugins/shared/workflows_management/public/features/debug-graph/workflow_graph_layout.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,16 @@
1010
import dagre from '@dagrejs/dagre';
1111
import type { Node } from '@xyflow/react';
1212
import { Position } from '@xyflow/react';
13+
import type { WorkflowGraph } from '@kbn/workflows/graph';
1314

1415
/**
1516
* Converts a workflow graph into positioned ReactFlow nodes and edges using Dagre layout algorithm.
1617
*
1718
* @param graph - The dagre graph representation of the workflow
1819
* @returns Object containing positioned nodes and edges for ReactFlow
1920
*/
20-
export function convertWorkflowGraphToReactFlow(graph: dagre.graphlib.Graph) {
21-
const topologySort = dagre.graphlib.alg.topsort(graph);
21+
export function convertWorkflowGraphToReactFlow(graph: WorkflowGraph) {
22+
const topologySort = graph.topologicalOrder;
2223
const dagreGraph = new dagre.graphlib.Graph();
2324
dagreGraph.setGraph({});
2425
dagreGraph.setDefaultEdgeLabel(() => ({}));
@@ -38,7 +39,7 @@ export function convertWorkflowGraphToReactFlow(graph: dagre.graphlib.Graph) {
3839
let maxDepth = 0;
3940

4041
topologySort
41-
.map((nodeId) => graph.node(nodeId))
42+
.map((nodeId) => graph.getNode(nodeId))
4243
.forEach((node: any) => {
4344
if (node.type.startsWith('exit')) {
4445
stack.pop();
@@ -70,7 +71,7 @@ export function convertWorkflowGraphToReactFlow(graph: dagre.graphlib.Graph) {
7071
})
7172
);
7273

73-
graph.edges().forEach((edge) => {
74+
graph.getEdges().forEach((edge) => {
7475
// Reverse source and destination for BT layout
7576
dagreGraph.setEdge(edge.w, edge.v, {
7677
type: 'workflowEdge',
@@ -79,16 +80,16 @@ export function convertWorkflowGraphToReactFlow(graph: dagre.graphlib.Graph) {
7980

8081
dagre.layout(dagreGraph);
8182

82-
const nodes = graph.nodes().map((id) => {
83-
const dagreNode = dagreGraph.node(id);
84-
const graphNode = graph.node(id) as any;
83+
const nodes = graph.getAllNodes().map((graphNode) => {
84+
const dagreNode = dagreGraph.node(graphNode.id);
85+
8586
return {
86-
id,
87+
id: graphNode.id,
8788
data: {
8889
...dagreNode,
8990
stepType: graphNode?.type,
90-
step: graphNode?.configuration,
91-
label: graphNode?.label || id,
91+
step: (graphNode as any)?.configuration,
92+
label: graphNode?.id,
9293
},
9394
// See this: https://github.com/dagrejs/dagre/issues/287
9495
targetPosition: Position.Bottom, // Reversed due to BT layout
@@ -102,11 +103,11 @@ export function convertWorkflowGraphToReactFlow(graph: dagre.graphlib.Graph) {
102103
} as Node;
103104
});
104105

105-
const edges = graph.edges().map((e) => ({
106+
const edges = graph.getEdges().map((e) => ({
106107
id: `${e.v} -> ${e.w}`,
107108
source: e.v,
108109
target: e.w,
109-
label: graph.edge(e)?.label,
110+
label: graph.getEdge(e)?.label,
110111
}));
111112

112113
return { nodes, edges };

src/platform/plugins/shared/workflows_management/public/pages/workflow_detail/ui/workflow_detail_page.tsx

Lines changed: 69 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { WorkflowDetailHeader } from './workflow_detail_header';
2525
import { WorkflowEditor } from './workflow_editor';
2626
import { WorkflowEditorLayout } from './workflow_detail_layout';
2727
import { getWorkflowZodSchemaLoose } from '../../../../common/schema';
28+
import { WorkflowEditorStoreProvider } from '../../../widgets/workflow_yaml_editor/lib/store';
2829

2930
export function WorkflowDetailPage({ id }: { id: string }) {
3031
const { application, notifications } = useKibana().services;
@@ -244,74 +245,76 @@ export function WorkflowDetailPage({ id }: { id: string }) {
244245
}
245246

246247
return (
247-
<EuiFlexGroup gutterSize="none" style={{ height: '100%' }}>
248-
<EuiFlexItem style={{ overflow: 'hidden' }}>
249-
<WorkflowDetailHeader
250-
name={workflow?.name}
251-
isLoading={isLoadingWorkflow}
252-
activeTab={activeTab}
253-
canRunWorkflow={canRunWorkflow}
254-
canSaveWorkflow={canSaveWorkflow}
255-
isValid={workflow?.valid ?? true}
256-
isEnabled={workflow?.enabled ?? false}
257-
handleRunClick={() => handleRunClick({ test: false })}
258-
handleSave={handleSave}
259-
handleToggleWorkflow={handleToggleWorkflow}
260-
canTestWorkflow={canTestWorkflow}
261-
handleTestClick={() => handleRunClick({ test: true })}
262-
handleTabChange={setActiveTab}
263-
hasUnsavedChanges={hasChanges}
264-
highlightDiff={highlightDiff}
265-
setHighlightDiff={setHighlightDiff}
266-
lastUpdatedAt={workflow?.lastUpdatedAt ?? null}
267-
/>
268-
<WorkflowEditorLayout
269-
editor={
270-
<WorkflowEditor
271-
workflow={workflow}
272-
workflowYaml={yamlValue}
273-
onWorkflowYamlChange={handleChange}
274-
hasChanges={hasChanges}
275-
execution={execution}
276-
activeTab={activeTab}
277-
selectedExecutionId={selectedExecutionId}
278-
selectedStepId={selectedStepId}
279-
highlightDiff={highlightDiff}
280-
setSelectedExecution={setSelectedExecution}
281-
/>
282-
}
283-
executionList={
284-
activeTab === 'executions' && workflow && !selectedExecutionId ? (
285-
<WorkflowExecutionList workflowId={workflow?.id ?? null} />
286-
) : null
287-
}
288-
executionDetail={
289-
workflow && selectedExecutionId ? (
290-
<WorkflowExecutionDetail
291-
workflowExecutionId={selectedExecutionId}
248+
<WorkflowEditorStoreProvider>
249+
<EuiFlexGroup gutterSize="none" style={{ height: '100%' }}>
250+
<EuiFlexItem style={{ overflow: 'hidden' }}>
251+
<WorkflowDetailHeader
252+
name={workflow?.name}
253+
isLoading={isLoadingWorkflow}
254+
activeTab={activeTab}
255+
canRunWorkflow={canRunWorkflow}
256+
canSaveWorkflow={canSaveWorkflow}
257+
isValid={workflow?.valid ?? true}
258+
isEnabled={workflow?.enabled ?? false}
259+
handleRunClick={() => handleRunClick({ test: false })}
260+
handleSave={handleSave}
261+
handleToggleWorkflow={handleToggleWorkflow}
262+
canTestWorkflow={canTestWorkflow}
263+
handleTestClick={() => handleRunClick({ test: true })}
264+
handleTabChange={setActiveTab}
265+
hasUnsavedChanges={hasChanges}
266+
highlightDiff={highlightDiff}
267+
setHighlightDiff={setHighlightDiff}
268+
lastUpdatedAt={workflow?.lastUpdatedAt ?? null}
269+
/>
270+
<WorkflowEditorLayout
271+
editor={
272+
<WorkflowEditor
273+
workflow={workflow}
292274
workflowYaml={yamlValue}
293-
showBackButton={activeTab === 'executions'}
294-
onClose={() => setSelectedExecution(null)}
275+
onWorkflowYamlChange={handleChange}
276+
hasChanges={hasChanges}
277+
execution={execution}
278+
activeTab={activeTab}
279+
selectedExecutionId={selectedExecutionId}
280+
selectedStepId={selectedStepId}
281+
highlightDiff={highlightDiff}
282+
setSelectedExecution={setSelectedExecution}
295283
/>
296-
) : null
297-
}
298-
/>
299-
</EuiFlexItem>
284+
}
285+
executionList={
286+
activeTab === 'executions' && workflow && !selectedExecutionId ? (
287+
<WorkflowExecutionList workflowId={workflow?.id ?? null} />
288+
) : null
289+
}
290+
executionDetail={
291+
workflow && selectedExecutionId ? (
292+
<WorkflowExecutionDetail
293+
workflowExecutionId={selectedExecutionId}
294+
workflowYaml={yamlValue}
295+
showBackButton={activeTab === 'executions'}
296+
onClose={() => setSelectedExecution(null)}
297+
/>
298+
) : null
299+
}
300+
/>
301+
</EuiFlexItem>
300302

301-
{workflowExecuteModalOpen && workflow && (
302-
<WorkflowExecuteModal
303-
definition={workflow.definition}
304-
onClose={() => setWorkflowExecuteModalOpen(false)}
305-
onSubmit={handleRunWorkflow}
306-
/>
307-
)}
308-
{testWorkflowModalOpen && definitionFromCurrentYaml && (
309-
<WorkflowExecuteModal
310-
definition={definitionFromCurrentYaml}
311-
onClose={() => setTestWorkflowModalOpen(false)}
312-
onSubmit={handleTestRunWorkflow}
313-
/>
314-
)}
315-
</EuiFlexGroup>
303+
{workflowExecuteModalOpen && workflow && (
304+
<WorkflowExecuteModal
305+
definition={workflow.definition}
306+
onClose={() => setWorkflowExecuteModalOpen(false)}
307+
onSubmit={handleRunWorkflow}
308+
/>
309+
)}
310+
{testWorkflowModalOpen && definitionFromCurrentYaml && (
311+
<WorkflowExecuteModal
312+
definition={definitionFromCurrentYaml}
313+
onClose={() => setTestWorkflowModalOpen(false)}
314+
onSubmit={handleTestRunWorkflow}
315+
/>
316+
)}
317+
</EuiFlexGroup>
318+
</WorkflowEditorStoreProvider>
316319
);
317320
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
export { useFocusedStepOutline } from './use_focused_step_outline';
11+
export { useStepDecorationsInExecution } from './use_step_decorations_in_execution';

0 commit comments

Comments
 (0)