Skip to content

Commit 84554e6

Browse files
zelzhouZelin Zhoulaileni-aws
authored
feat(stepfunctions): Start Execution fills existing ExecutionInput (#8147)
## Problem 1. We should auto fill the existing ExecutionInput when start a new execution in ExecutionDetails tab. 2. Circular dependencies exists in the StepFunctions extension logic, we need to fix those circular dependency in order for the build to pass. ## Solution 1. Allow the CDN bundle to pass back the ExecutionInput when invoking `startExecutionMessageHandler` and added ExecutionInput parameter to `executeStateMachine` command. 2. The circular dependencies were caused by a chain: `ExecutionDetailProvider` → `handleMessage` → `utils` → `executeStateMachine` → `ExecutionDetailProvider`. Separating `utils.ts` into two files broke part of the cycle by removing webview functions from the utils that `handleMessage` needed. However, a direct cycle remained between `executeStateMachine` and `ExecutionDetailProvider` and we cannot break this cycle by separating the utils, we introduced a callback to openExecutionDetails in `handleMessage` -> `executeStateMachine` so that we don't need to static import `ExecutionDetailProvider` anymore. ## Demo ![StartExecutionFills](https://github.com/user-attachments/assets/2177bfd5-579a-4b0b-ae73-923246e8fbf8) --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --------- Co-authored-by: Zelin Zhou <[email protected]> Co-authored-by: Laxman Reddy <[email protected]>
1 parent 8795c61 commit 84554e6

File tree

9 files changed

+136
-80
lines changed

9 files changed

+136
-80
lines changed

packages/core/src/awsexplorer/activation.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { getSourceNode } from '../shared/utilities/treeNodeUtils'
3737
import { openAwsCFNConsoleCommand, openAwsConsoleCommand } from '../shared/awsConsole'
3838
import { StackNameNode } from '../awsService/appBuilder/explorer/nodes/deployedStack'
3939
import { LambdaFunctionNodeDecorationProvider } from '../lambda/explorer/lambdaFunctionNodeDecorationProvider'
40+
import { ExecutionDetailProvider } from '../stepFunctions/executionDetails/executionDetailProvider'
4041

4142
/**
4243
* Activates the AWS Explorer UI and related functionality.
@@ -192,7 +193,12 @@ async function registerAwsExplorerCommands(
192193
context.extensionContext.subscriptions.push(
193194
Commands.register(
194195
'aws.executeStateMachine',
195-
async (node: StateMachineNode) => await executeStateMachine(context, node)
196+
async (node: StateMachineNode) =>
197+
await executeStateMachine(
198+
context,
199+
node,
200+
ExecutionDetailProvider.openExecutionDetails.bind(ExecutionDetailProvider)
201+
)
196202
),
197203
Commands.register('aws.copyArn', async (node: AWSResourceNode | TreeNode) => {
198204
const sourceNode = getSourceNode<AWSResourceNode>(node)

packages/core/src/stepFunctions/commands/downloadStateMachineDefinition.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { Result } from '../../shared/telemetry/telemetry'
1717
import { StateMachineNode } from '../explorer/stepFunctionsNodes'
1818
import { telemetry } from '../../shared/telemetry/telemetry'
1919
import { fs } from '../../shared/fs/fs'
20-
import { openWorkflowStudioWithDefinition } from '../utils'
20+
import { openWorkflowStudioWithDefinition } from '../stepFunctionsWorkflowStudioUtils'
2121

2222
export async function downloadStateMachineDefinition(params: {
2323
outputChannel: vscode.OutputChannel

packages/core/src/stepFunctions/executionDetails/executionDetailProvider.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ export class ExecutionDetailProvider {
115115
loaderNotification: undefined,
116116
executionArn,
117117
startTime,
118+
openExecutionDetails: ExecutionDetailProvider.openExecutionDetails.bind(ExecutionDetailProvider),
118119
}
119120

120121
// Handle messages from the webview

packages/core/src/stepFunctions/executionDetails/handleMessage.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,17 @@ import {
1111
ExecutionDetailsContext,
1212
ApiCallRequestMessage,
1313
InitResponseMessage,
14+
StartExecutionMessage,
1415
} from '../messageHandlers/types'
1516
import {
1617
loadStageMessageHandler,
1718
handleUnsupportedMessage,
1819
apiCallMessageHandler,
1920
} from '../messageHandlers/handleMessageHelpers'
20-
import { parseExecutionArnForStateMachine, openWorkflowStudio, showExecuteStateMachineWebview } from '../utils'
21+
import { parseExecutionArnForStateMachine } from '../utils'
2122
import { getLogger } from '../../shared/logger/logger'
23+
import { openWorkflowStudio } from '../stepFunctionsWorkflowStudioUtils'
24+
import { showExecuteStateMachineWebview } from '../vue/executeStateMachine/executeStateMachine'
2225

2326
/**
2427
* Handles messages received from the ExecutionDetails webview. Depending on the message type and command,
@@ -39,7 +42,7 @@ export async function handleMessage(message: Message, context: ExecutionDetailsC
3942
break
4043
}
4144
case Command.START_EXECUTION:
42-
void startExecutionMessageHandler(context)
45+
void startExecutionMessageHandler(message as StartExecutionMessage, context)
4346
break
4447
case Command.EDIT_STATE_MACHINE:
4548
void editStateMachineMessageHandler(context)
@@ -85,7 +88,7 @@ async function initMessageHandler(context: ExecutionDetailsContext) {
8588
}
8689
}
8790

88-
async function startExecutionMessageHandler(context: ExecutionDetailsContext) {
91+
async function startExecutionMessageHandler(message: StartExecutionMessage, context: ExecutionDetailsContext) {
8992
const logger = getLogger('stepfunctions')
9093
try {
9194
// Parsing execution ARN to get state machine info
@@ -100,6 +103,8 @@ async function startExecutionMessageHandler(context: ExecutionDetailsContext) {
100103
arn: stateMachineArn,
101104
name: stateMachineName,
102105
region: region,
106+
openExecutionDetails: context.openExecutionDetails,
107+
executionInput: message.executionInput,
103108
})
104109
} catch (error) {
105110
logger.error('Start execution failed: %O', error)

packages/core/src/stepFunctions/messageHandlers/types.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,16 @@ export interface WebviewContext extends BaseContext {
3535
fileId: string
3636
}
3737

38+
export type OpenExecutionDetailsCallBack = (
39+
executionArn: string,
40+
startTime?: string,
41+
params?: vscode.WebviewPanelOptions & vscode.WebviewOptions
42+
) => Promise<void>
43+
3844
export interface ExecutionDetailsContext extends BaseContext {
3945
executionArn: string
4046
startTime?: string
47+
openExecutionDetails: OpenExecutionDetailsCallBack
4148
}
4249

4350
export type LoaderNotification = {
@@ -105,6 +112,10 @@ export interface SyncFileRequestMessage extends SaveFileRequestMessage {
105112
fileContents: string
106113
}
107114

115+
export interface StartExecutionMessage extends Message {
116+
executionInput?: string
117+
}
118+
108119
export enum ApiAction {
109120
IAMListRoles = 'iam:ListRoles',
110121
SFNTestState = 'sfn:TestState',
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import * as StepFunctions from '@aws-sdk/client-sfn'
7+
import * as vscode from 'vscode'
8+
import { StepFunctionsClient } from '../shared/clients/stepFunctions'
9+
import { WorkflowStudioEditorProvider } from './workflowStudio/workflowStudioEditorProvider'
10+
11+
/**
12+
* Opens a state machine definition in Workflow Studio
13+
* @param stateMachineArn The ARN of the state machine
14+
* @param region The AWS region
15+
*/
16+
export const openWorkflowStudio = async (stateMachineArn: string, region: string) => {
17+
const client: StepFunctionsClient = new StepFunctionsClient(region)
18+
const stateMachineDetails: StepFunctions.DescribeStateMachineCommandOutput = await client.getStateMachineDetails({
19+
stateMachineArn,
20+
})
21+
22+
await openWorkflowStudioWithDefinition(stateMachineDetails.definition)
23+
}
24+
25+
/**
26+
* Opens a state machine definition in Workflow Studio using pre-fetched definition content
27+
* @param definition The state machine definition content
28+
* @param options Optional webview configuration options
29+
*/
30+
export const openWorkflowStudioWithDefinition = async (
31+
definition: string | undefined,
32+
options?: {
33+
preserveFocus?: boolean
34+
viewColumn?: vscode.ViewColumn
35+
}
36+
) => {
37+
const doc = await vscode.workspace.openTextDocument({
38+
language: 'asl',
39+
content: definition,
40+
})
41+
42+
const textEditor = await vscode.window.showTextDocument(doc)
43+
await WorkflowStudioEditorProvider.openWithWorkflowStudio(textEditor.document.uri, {
44+
preserveFocus: options?.preserveFocus ?? false,
45+
viewColumn: options?.viewColumn ?? vscode.ViewColumn.One,
46+
})
47+
}

packages/core/src/stepFunctions/utils.ts

Lines changed: 0 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,6 @@ import {
1717
} from 'amazon-states-language-service'
1818
import { fromExtensionManifest } from '../shared/settings'
1919
import { IamRole } from '../shared/clients/iam'
20-
import { WorkflowStudioEditorProvider } from './workflowStudio/workflowStudioEditorProvider'
21-
import { VueWebview } from '../webviews/main'
22-
import { ExecuteStateMachineWebview } from './vue/executeStateMachine/executeStateMachine'
23-
import globals from '../shared/extensionGlobals'
2420

2521
const documentSettings: DocumentLanguageSettings = { comments: 'error', trailingCommas: 'error' }
2622
const languageService = getLanguageService({})
@@ -135,71 +131,6 @@ export const parseExecutionArnForStateMachine = (executionArn: string) => {
135131
}
136132
}
137133

138-
/**
139-
* Opens a state machine definition in Workflow Studio
140-
* @param stateMachineArn The ARN of the state machine
141-
* @param region The AWS region
142-
*/
143-
export const openWorkflowStudio = async (stateMachineArn: string, region: string) => {
144-
const client: StepFunctionsClient = new StepFunctionsClient(region)
145-
const stateMachineDetails: StepFunctions.DescribeStateMachineCommandOutput = await client.getStateMachineDetails({
146-
stateMachineArn,
147-
})
148-
149-
await openWorkflowStudioWithDefinition(stateMachineDetails.definition)
150-
}
151-
152-
/**
153-
* Opens a state machine definition in Workflow Studio using pre-fetched definition content
154-
* @param definition The state machine definition content
155-
* @param options Optional webview configuration options
156-
*/
157-
export const openWorkflowStudioWithDefinition = async (
158-
definition: string | undefined,
159-
options?: {
160-
preserveFocus?: boolean
161-
viewColumn?: vscode.ViewColumn
162-
}
163-
) => {
164-
const doc = await vscode.workspace.openTextDocument({
165-
language: 'asl',
166-
content: definition,
167-
})
168-
169-
const textEditor = await vscode.window.showTextDocument(doc)
170-
await WorkflowStudioEditorProvider.openWithWorkflowStudio(textEditor.document.uri, {
171-
preserveFocus: options?.preserveFocus ?? false,
172-
viewColumn: options?.viewColumn ?? vscode.ViewColumn.One,
173-
})
174-
}
175-
176-
/**
177-
* Shows the Execute State Machine webview with the provided state machine data
178-
* @param extensionContext The extension context
179-
* @param outputChannel The output channel for logging
180-
* @param stateMachineData Object containing arn, name, and region of the state machine
181-
* @returns The webview instance
182-
*/
183-
export const showExecuteStateMachineWebview = async (stateMachineData: {
184-
arn: string
185-
name: string
186-
region: string
187-
}) => {
188-
const Panel = VueWebview.compilePanel(ExecuteStateMachineWebview)
189-
const wv = new Panel(globals.context, globals.outputChannel, {
190-
arn: stateMachineData.arn,
191-
name: stateMachineData.name,
192-
region: stateMachineData.region,
193-
})
194-
195-
await wv.show({
196-
title: localize('AWS.executeStateMachine.title', 'Start Execution'),
197-
cssFiles: ['executeStateMachine.css'],
198-
})
199-
200-
return wv
201-
}
202-
203134
const isInvalidJson = (content: string): boolean => {
204135
try {
205136
JSON.parse(content)

packages/core/src/stepFunctions/vue/executeStateMachine/executeStateMachine.ts

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@ import { ExtContext } from '../../../shared/extensions'
1515
import { VueWebview } from '../../../webviews/main'
1616
import * as vscode from 'vscode'
1717
import { telemetry } from '../../../shared/telemetry/telemetry'
18-
import { ExecutionDetailProvider } from '../../executionDetails/executionDetailProvider'
19-
import { showExecuteStateMachineWebview } from '../../utils'
18+
import globals from '../../../shared/extensionGlobals'
19+
import { OpenExecutionDetailsCallBack } from '../../messageHandlers/types'
2020

2121
interface StateMachine {
2222
arn: string
2323
name: string
2424
region: string
25+
openExecutionDetails: OpenExecutionDetailsCallBack
26+
executionInput?: string
2527
}
2628

2729
export class ExecuteStateMachineWebview extends VueWebview {
@@ -63,7 +65,7 @@ export class ExecuteStateMachineWebview extends VueWebview {
6365
stateMachineArn: this.stateMachine.arn,
6466
input,
6567
})
66-
await ExecutionDetailProvider.openExecutionDetails(
68+
await this.stateMachine.openExecutionDetails(
6769
startExecResponse.executionArn!,
6870
startExecResponse.startDate!.toString()
6971
)
@@ -89,11 +91,49 @@ export class ExecuteStateMachineWebview extends VueWebview {
8991
}
9092
}
9193

92-
export async function executeStateMachine(context: ExtContext, node: StateMachineNode): Promise<void> {
94+
/**
95+
* Shows the Execute State Machine webview with the provided state machine data
96+
* @param extensionContext The extension context
97+
* @param outputChannel The output channel for logging
98+
* @param stateMachineData Object containing arn, name, region, and optional executionInput of the state machine
99+
* @returns The webview instance
100+
*/
101+
export const showExecuteStateMachineWebview = async (stateMachineData: {
102+
arn: string
103+
name: string
104+
region: string
105+
openExecutionDetails: OpenExecutionDetailsCallBack
106+
executionInput?: string
107+
}) => {
108+
const Panel = VueWebview.compilePanel(ExecuteStateMachineWebview)
109+
const wv = new Panel(globals.context, globals.outputChannel, {
110+
arn: stateMachineData.arn,
111+
name: stateMachineData.name,
112+
region: stateMachineData.region,
113+
openExecutionDetails: stateMachineData.openExecutionDetails,
114+
executionInput: stateMachineData.executionInput,
115+
})
116+
117+
await wv.show({
118+
title: localize('AWS.executeStateMachine.title', 'Start Execution'),
119+
cssFiles: ['executeStateMachine.css'],
120+
})
121+
122+
return wv
123+
}
124+
125+
export async function executeStateMachine(
126+
context: ExtContext,
127+
node: StateMachineNode,
128+
openExecutionDetails: OpenExecutionDetailsCallBack,
129+
executionInput?: string
130+
): Promise<void> {
93131
await showExecuteStateMachineWebview({
94132
arn: node.details.stateMachineArn || '',
95133
name: node.details.name || '',
96134
region: node.regionCode,
135+
openExecutionDetails,
136+
executionInput,
97137
})
98138
telemetry.stepfunctions_executeStateMachineView.emit()
99139
}

packages/core/src/stepFunctions/vue/executeStateMachine/executeStateMachine.vue

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,15 @@ const defaultInitialData = {
5555
name: '',
5656
region: '',
5757
arn: '',
58+
executionInput: '',
5859
}
5960
6061
export default defineComponent({
6162
async created() {
6263
this.initialData = (await client.init()) ?? this.initialData
64+
if (this.initialData.executionInput) {
65+
this.executionInput = this.formatJson(this.initialData.executionInput)
66+
}
6367
},
6468
data: () => ({
6569
initialData: defaultInitialData,
@@ -89,7 +93,9 @@ export default defineComponent({
8993
break
9094
case 'textarea':
9195
this.placeholderJson = defaultJsonPlaceholder
92-
this.executionInput = ''
96+
if (!this.initialData.executionInput) {
97+
this.executionInput = ''
98+
}
9399
this.fileInputVisible = false
94100
break
95101
}
@@ -104,7 +110,7 @@ export default defineComponent({
104110
reader.onload = (event) => {
105111
if (event.target) {
106112
const result = event.target.result
107-
this.executionInput = result as string
113+
this.executionInput = this.formatJson(result as string)
108114
}
109115
} // desired file content
110116
reader.onerror = (error) => {
@@ -115,6 +121,15 @@ export default defineComponent({
115121
this.textAreaVisible = true
116122
}
117123
},
124+
formatJson: function (jsonString: string): string {
125+
try {
126+
const parsed = JSON.parse(jsonString)
127+
return JSON.stringify(parsed, null, 2)
128+
} catch (error) {
129+
console.warn('Failed to format JSON:', error)
130+
return jsonString
131+
}
132+
},
118133
sendInput: function () {
119134
client.executeStateMachine(this.executionInput || '{}')
120135
},

0 commit comments

Comments
 (0)