diff --git a/packages/core/package.nls.json b/packages/core/package.nls.json index 203b3762dc4..4bef50bcb2d 100644 --- a/packages/core/package.nls.json +++ b/packages/core/package.nls.json @@ -32,7 +32,6 @@ "AWS.stepFunctions.executeStateMachine.info.executing": "Starting execution of '{0}' in {1}...", "AWS.stepFunctions.executeStateMachine.info.started": "Execution started", "AWS.stepFunctions.executeStateMachine.error.failedToStart": "There was an error starting execution for '{0}', check AWS Toolkit logs for more information.", - "AWS.stepfunctions.viewExecutionDetailsByExecutionARN": "View Execution Details by Execution ARN", "AWS.stepFunctions.asl.format.enable.desc": "Enables the default formatter used with Amazon States Language files", "AWS.stepFunctions.asl.maxItemsComputed.desc": "The maximum number of outline symbols and folding regions computed (limited for performance reasons).", "AWS.stepFunctions.workflowStudio.actions.progressMessage": "Opening asl file in Workflow Studio", @@ -229,6 +228,7 @@ "AWS.command.stepFunctions.createStateMachineFromTemplate": "Create a new Step Functions state machine", "AWS.command.stepFunctions.publishStateMachine": "Publish state machine to Step Functions", "AWS.command.stepFunctions.openWithWorkflowStudio": "Open with Workflow Studio", + "AWS.command.stepFunctions.viewExecutionDetailsByExecutionARN": "View Execution Details by Execution ARN", "AWS.command.cdk.previewStateMachine": "Render state machine graph from CDK application", "AWS.command.copyLogResource": "Copy Log Stream or Group", "AWS.command.saveCurrentLogDataContent": "Save Log to File", diff --git a/packages/core/src/shared/clients/stepFunctions.ts b/packages/core/src/shared/clients/stepFunctions.ts index 22483307654..a6523042107 100644 --- a/packages/core/src/shared/clients/stepFunctions.ts +++ b/packages/core/src/shared/clients/stepFunctions.ts @@ -7,17 +7,35 @@ import { CreateStateMachineCommand, CreateStateMachineCommandInput, CreateStateMachineCommandOutput, + DescribeExecutionCommand, + DescribeExecutionCommandInput, + DescribeExecutionCommandOutput, + DescribeMapRunCommand, + DescribeMapRunCommandInput, + DescribeMapRunCommandOutput, DescribeStateMachineCommand, DescribeStateMachineCommandInput, DescribeStateMachineCommandOutput, + DescribeStateMachineForExecutionCommand, + DescribeStateMachineForExecutionCommandInput, + DescribeStateMachineForExecutionCommandOutput, + GetExecutionHistoryCommand, + GetExecutionHistoryCommandInput, + GetExecutionHistoryCommandOutput, ListStateMachinesCommand, ListStateMachinesCommandInput, ListStateMachinesCommandOutput, + RedriveExecutionCommand, + RedriveExecutionCommandInput, + RedriveExecutionCommandOutput, SFNClient, StartExecutionCommand, StartExecutionCommandInput, StartExecutionCommandOutput, StateMachineListItem, + StopExecutionCommand, + StopExecutionCommandInput, + StopExecutionCommandOutput, TestStateCommand, TestStateCommandInput, TestStateCommandOutput, @@ -50,10 +68,38 @@ export class StepFunctionsClient extends ClientWrapper { return this.makeRequest(DescribeStateMachineCommand, request) } + public async describeStateMachineForExecution( + request: DescribeStateMachineForExecutionCommandInput + ): Promise { + return this.makeRequest(DescribeStateMachineForExecutionCommand, request) + } + + public async describeExecution(request: DescribeExecutionCommandInput): Promise { + return this.makeRequest(DescribeExecutionCommand, request) + } + + public async describeMapRun(request: DescribeMapRunCommandInput): Promise { + return this.makeRequest(DescribeMapRunCommand, request) + } + + public async getExecutionHistory( + request: GetExecutionHistoryCommandInput + ): Promise { + return this.makeRequest(GetExecutionHistoryCommand, request) + } + + public async reDriveExecution(request: RedriveExecutionCommandInput): Promise { + return this.makeRequest(RedriveExecutionCommand, request) + } + public async executeStateMachine(request: StartExecutionCommandInput): Promise { return this.makeRequest(StartExecutionCommand, request) } + public async stopExecution(request: StopExecutionCommandInput): Promise { + return this.makeRequest(StopExecutionCommand, request) + } + public async createStateMachine(request: CreateStateMachineCommandInput): Promise { return this.makeRequest(CreateStateMachineCommand, request) } diff --git a/packages/core/src/stepFunctions/commands/visualizeStateMachine/renderStateMachineGraphCDK.ts b/packages/core/src/stepFunctions/commands/visualizeStateMachine/renderStateMachineGraphCDK.ts index 0a473e80201..0fb10c87db5 100644 --- a/packages/core/src/stepFunctions/commands/visualizeStateMachine/renderStateMachineGraphCDK.ts +++ b/packages/core/src/stepFunctions/commands/visualizeStateMachine/renderStateMachineGraphCDK.ts @@ -12,7 +12,7 @@ import { isTreeNode } from '../../../shared/treeview/resourceTreeDataProvider' import { unboxTreeNode } from '../../../shared/treeview/utils' import { Commands } from '../../../shared/vscode/commands2' import { PreviewStateMachineCDKWizard } from '../../wizards/previewStateMachineCDKWizard' -import { WorkflowMode } from '../../workflowStudio/types' +import { WorkflowMode } from '../../messageHandlers/types' import { WorkflowStudioEditorProvider } from '../../workflowStudio/workflowStudioEditorProvider' import { getStateMachineDefinitionFromCfnTemplate } from './getStateMachineDefinitionFromCfnTemplate' diff --git a/packages/core/src/stepFunctions/executionDetails/executionDetailProvider.ts b/packages/core/src/stepFunctions/executionDetails/executionDetailProvider.ts index 7ad945aba55..d1420d36bde 100644 --- a/packages/core/src/stepFunctions/executionDetails/executionDetailProvider.ts +++ b/packages/core/src/stepFunctions/executionDetails/executionDetailProvider.ts @@ -8,8 +8,10 @@ import { getLogger } from '../../shared/logger/logger' import request from '../../shared/request' import { ToolkitError } from '../../shared/errors' import { i18n } from '../../shared/i18n-helper' -import { ComponentType } from '../workflowStudio/types' +import { ComponentType } from '../messageHandlers/types' import { isLocalDev, localhost, cdn } from '../constants/webviewResources' +import { handleMessage } from './handleMessage' +import { ExecutionDetailsContext } from '../messageHandlers/types' /** * Provider for Execution Details panels. @@ -98,11 +100,16 @@ export class ExecutionDetailProvider { // Set up the content panel.webview.html = await this.getWebviewContent() + const context: ExecutionDetailsContext = { + panel, + loaderNotification: undefined, + executionArn, + } // Handle messages from the webview panel.webview.onDidReceiveMessage(async (message) => { this.logger.debug('Received message from execution details webview: %O', message) - // Add message handlers as needed + await handleMessage(message, context) }) } catch (err) { void vscode.window.showErrorMessage(i18n('AWS.stepFunctions.executionDetails.failed')) diff --git a/packages/core/src/stepFunctions/executionDetails/handleMessage.ts b/packages/core/src/stepFunctions/executionDetails/handleMessage.ts new file mode 100644 index 00000000000..3dce63b22b4 --- /dev/null +++ b/packages/core/src/stepFunctions/executionDetails/handleMessage.ts @@ -0,0 +1,75 @@ +/* +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + Command, + Message, + MessageType, + ExecutionDetailsContext, + ApiCallRequestMessage, + InitResponseMessage, +} from '../messageHandlers/types' +import { + loadStageMessageHandler, + handleUnsupportedMessage, + apiCallMessageHandler, +} from '../messageHandlers/handleMessageHelpers' + +/** + * Handles messages received from the ExecutionDetails webview. Depending on the message type and command, + * calls the appropriate handler function + * @param message The message received from the webview + * @param context The context object containing information about the execution details webview environment + */ +export async function handleMessage(message: Message, context: ExecutionDetailsContext) { + const { command, messageType } = message + if (messageType === MessageType.REQUEST) { + switch (command) { + case Command.INIT: + void initMessageHandler(context) + break + case Command.API_CALL: + void apiCallMessageHandler(message as ApiCallRequestMessage, context) + break + default: + void handleUnsupportedMessage(context, message) + break + } + } else if (messageType === MessageType.BROADCAST) { + switch (command) { + case Command.LOAD_STAGE: + void loadStageMessageHandler(context) + break + default: + void handleUnsupportedMessage(context, message) + break + } + } else { + void handleUnsupportedMessage(context, message) + } +} + +/** + * Handler for when the webview is ready. + * This handler is used to initialize the webview with execution details. + * @param context The context object containing the necessary information for the webview. + */ +async function initMessageHandler(context: ExecutionDetailsContext) { + try { + await context.panel.webview.postMessage({ + messageType: MessageType.BROADCAST, + command: Command.INIT, + executionArn: context.executionArn, + }) + } catch (e) { + await context.panel.webview.postMessage({ + messageType: MessageType.RESPONSE, + command: Command.INIT, + isSuccess: false, + failureReason: (e as Error).message, + } as InitResponseMessage) + } +} diff --git a/packages/core/src/stepFunctions/messageHandlers/handleMessageHelpers.ts b/packages/core/src/stepFunctions/messageHandlers/handleMessageHelpers.ts new file mode 100644 index 00000000000..b0b628fa9c4 --- /dev/null +++ b/packages/core/src/stepFunctions/messageHandlers/handleMessageHelpers.ts @@ -0,0 +1,47 @@ +/* +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Command, Message, MessageType, BaseContext, ApiCallRequestMessage, UnsupportedMessage } from './types' +import { StepFunctionApiHandler } from './stepFunctionApiHandler' +import globals from '../../shared/extensionGlobals' +import { getLogger } from '../../shared/logger/logger' + +/** + * Handler for managing webview stage load, which updates load notifications. + * @param context The context object containing the necessary information for the webview. + */ +export async function loadStageMessageHandler(context: BaseContext) { + context.loaderNotification?.progress.report({ increment: 25 }) + setTimeout(() => { + context.loaderNotification?.resolve() + }, 100) +} + +/** + * Handler for making API calls from the webview and returning the response. + * @param request The request message containing the API to call and the parameters + * @param context The webview context used for returning the API response to the webview + */ +export function apiCallMessageHandler(request: ApiCallRequestMessage, context: BaseContext) { + const logger = getLogger('stepfunctions') + const apiHandler = new StepFunctionApiHandler(globals.awsContext.getCredentialDefaultRegion(), context) + apiHandler.performApiCall(request).catch((error) => logger.error('%s API call failed: %O', request.apiName, error)) +} + +/** + * Handles unsupported or unrecognized messages by sending a response to the webview. Ensures compatibility with future + * commands and message types, preventing issues if the user has an outdated extension version. + * @param context The context object containing information about the webview environment + * @param command The command received from the webview + * @param messageType The type of the message received + */ +export async function handleUnsupportedMessage(context: BaseContext, originalMessage: Message) { + await context.panel.webview.postMessage({ + messageType: MessageType.RESPONSE, + command: Command.UNSUPPORTED_COMMAND, + originalMessage, + } as UnsupportedMessage) +} diff --git a/packages/core/src/stepFunctions/workflowStudio/workflowStudioApiHandler.ts b/packages/core/src/stepFunctions/messageHandlers/stepFunctionApiHandler.ts similarity index 65% rename from packages/core/src/stepFunctions/workflowStudio/workflowStudioApiHandler.ts rename to packages/core/src/stepFunctions/messageHandlers/stepFunctionApiHandler.ts index 6c0fb850b9d..4a466d9f4ab 100644 --- a/packages/core/src/stepFunctions/workflowStudio/workflowStudioApiHandler.ts +++ b/packages/core/src/stepFunctions/messageHandlers/stepFunctionApiHandler.ts @@ -6,14 +6,14 @@ import * as StepFunctions from '@aws-sdk/client-sfn' import { IamClient, IamRole } from '../../shared/clients/iam' import { StepFunctionsClient } from '../../shared/clients/stepFunctions' -import { ApiAction, ApiCallRequestMessage, Command, MessageType, WebviewContext } from './types' +import { ApiAction, ApiCallRequestMessage, Command, MessageType, BaseContext } from './types' import { telemetry } from '../../shared/telemetry/telemetry' import { ListRolesRequest } from '@aws-sdk/client-iam' -export class WorkflowStudioApiHandler { +export class StepFunctionApiHandler { public constructor( region: string, - private readonly context: WebviewContext, + private readonly context: BaseContext, private readonly clients = { sfn: new StepFunctionsClient(region), iam: new IamClient(region), @@ -33,6 +33,30 @@ export class WorkflowStudioApiHandler { case ApiAction.SFNTestState: response = await this.testState(params) break + case ApiAction.SFNDescribeStateMachine: + response = await this.clients.sfn.getStateMachineDetails(params) + break + case ApiAction.SFNDescribeStateMachineForExecution: + response = await this.clients.sfn.describeStateMachineForExecution(params) + break + case ApiAction.SFNDescribeExecution: + response = await this.clients.sfn.describeExecution(params) + break + case ApiAction.SFNDescribeMapRun: + response = await this.clients.sfn.describeMapRun(params) + break + case ApiAction.SFNGetExecutionHistory: + response = await this.clients.sfn.getExecutionHistory(params) + break + case ApiAction.SFNRedriveExecution: + response = await this.clients.sfn.reDriveExecution(params) + break + case ApiAction.SFNStartExecution: + response = await this.clients.sfn.executeStateMachine(params) + break + case ApiAction.SFNStopExecution: + response = await this.clients.sfn.stopExecution(params) + break default: throw new Error(`Unknown API: ${apiName}`) } diff --git a/packages/core/src/stepFunctions/workflowStudio/types.ts b/packages/core/src/stepFunctions/messageHandlers/types.ts similarity index 62% rename from packages/core/src/stepFunctions/workflowStudio/types.ts rename to packages/core/src/stepFunctions/messageHandlers/types.ts index 73cb4dce901..43dd0b97468 100644 --- a/packages/core/src/stepFunctions/workflowStudio/types.ts +++ b/packages/core/src/stepFunctions/messageHandlers/types.ts @@ -16,20 +16,27 @@ export enum WorkflowMode { Readonly = 'readonly', } -export type WebviewContext = { +export interface BaseContext { + panel: vscode.WebviewPanel + loaderNotification: undefined | LoaderNotification +} + +export interface WebviewContext extends BaseContext { stateMachineName: string mode: WorkflowMode - panel: vscode.WebviewPanel textDocument: vscode.TextDocument disposables: vscode.Disposable[] workSpacePath: string defaultTemplatePath: string defaultTemplateName: string fileStates: Record - loaderNotification: undefined | LoaderNotification fileId: string } +export interface ExecutionDetailsContext extends BaseContext { + executionArn: string +} + export type LoaderNotification = { progress: vscode.Progress<{ message?: string | undefined @@ -96,11 +103,27 @@ export interface SyncFileRequestMessage extends SaveFileRequestMessage { export enum ApiAction { IAMListRoles = 'iam:ListRoles', SFNTestState = 'sfn:TestState', + SFNDescribeStateMachine = 'sfn:describeStateMachine', + SFNDescribeStateMachineForExecution = 'sfn:describeStateMachineForExecution', + SFNDescribeExecution = 'sfn:describeExecution', + SFNDescribeMapRun = 'sfn:describeMapRun', + SFNGetExecutionHistory = 'sfn:getExecutionHistory', + SFNRedriveExecution = 'sfn:redriveExecution', + SFNStartExecution = 'sfn:startExecution', + SFNStopExecution = 'sfn:stopExecution', } type ApiCallRequestMapping = { [ApiAction.IAMListRoles]: IAM.ListRolesRequest [ApiAction.SFNTestState]: StepFunctions.TestStateInput + [ApiAction.SFNDescribeStateMachine]: StepFunctions.DescribeStateMachineInput + [ApiAction.SFNDescribeStateMachineForExecution]: StepFunctions.DescribeStateMachineForExecutionInput + [ApiAction.SFNDescribeExecution]: StepFunctions.DescribeExecutionInput + [ApiAction.SFNDescribeMapRun]: StepFunctions.DescribeMapRunInput + [ApiAction.SFNGetExecutionHistory]: StepFunctions.GetExecutionHistoryInput + [ApiAction.SFNRedriveExecution]: StepFunctions.RedriveExecutionInput + [ApiAction.SFNStartExecution]: StepFunctions.StartExecutionInput + [ApiAction.SFNStopExecution]: StepFunctions.StopExecutionInput } interface ApiCallRequestMessageBase extends Message { @@ -115,3 +138,11 @@ interface ApiCallRequestMessageBase extends Message { export type ApiCallRequestMessage = | ApiCallRequestMessageBase | ApiCallRequestMessageBase + | ApiCallRequestMessageBase + | ApiCallRequestMessageBase + | ApiCallRequestMessageBase + | ApiCallRequestMessageBase + | ApiCallRequestMessageBase + | ApiCallRequestMessageBase + | ApiCallRequestMessageBase + | ApiCallRequestMessageBase diff --git a/packages/core/src/stepFunctions/workflowStudio/handleMessage.ts b/packages/core/src/stepFunctions/workflowStudio/handleMessage.ts index 2b548ab957f..fd656cea3c6 100644 --- a/packages/core/src/stepFunctions/workflowStudio/handleMessage.ts +++ b/packages/core/src/stepFunctions/workflowStudio/handleMessage.ts @@ -14,23 +14,25 @@ import { FileChangeEventTrigger, SyncFileRequestMessage, ApiCallRequestMessage, - UnsupportedMessage, WorkflowMode, -} from './types' +} from '../messageHandlers/types' import { submitFeedback } from '../../feedback/vue/submitFeedback' import { placeholder } from '../../shared/vscode/commands2' import * as nls from 'vscode-nls' import vscode from 'vscode' import { telemetry } from '../../shared/telemetry/telemetry' import { ToolkitError } from '../../shared/errors' -import { WorkflowStudioApiHandler } from './workflowStudioApiHandler' import globals from '../../shared/extensionGlobals' -import { getLogger } from '../../shared/logger/logger' import { publishStateMachine } from '../commands/publishStateMachine' import { getStateMachineDefinitionFromCfnTemplate, toUnescapedAslJsonString, } from '../commands/visualizeStateMachine/getStateMachineDefinitionFromCfnTemplate' +import { + loadStageMessageHandler, + handleUnsupportedMessage, + apiCallMessageHandler, +} from '../messageHandlers/handleMessageHelpers' const localize = nls.loadMessageBundle() @@ -143,18 +145,6 @@ export async function broadcastFileChange(context: WebviewContext, trigger: File } as FileChangedMessage) } -/** - * Handler for managing webview stage load, which updates load notifications. - * @param message The message containing the load stage. - * @param context The context object containing the necessary information for the webview. - */ -async function loadStageMessageHandler(context: WebviewContext) { - context.loaderNotification?.progress.report({ increment: 25 }) - setTimeout(() => { - context.loaderNotification?.resolve() - }, 100) -} - /** * Handler for closing WFS custom editor. When called, disposes webview panel and opens default VSCode editor * @param context The context object containing the necessary information for the webview. @@ -250,29 +240,3 @@ async function autoSyncFileMessageHandler(request: SyncFileRequestMessage, conte } }) } - -/** - * Handler for making API calls from the webview and returning the response. - * @param request The request message containing the API to call and the parameters - * @param context The webview context used for returning the API response to the webview - */ -function apiCallMessageHandler(request: ApiCallRequestMessage, context: WebviewContext) { - const logger = getLogger('stepfunctions') - const apiHandler = new WorkflowStudioApiHandler(globals.awsContext.getCredentialDefaultRegion(), context) - apiHandler.performApiCall(request).catch((error) => logger.error('%s API call failed: %O', request.apiName, error)) -} - -/** - * Handles unsupported or unrecognized messages by sending a response to the webview. Ensures compatibility with future - * commands and message types, preventing issues if the user has an outdated extension version. - * @param context The context object containing information about the webview environment - * @param command The command received from the webview - * @param messageType The type of the message received - */ -async function handleUnsupportedMessage(context: WebviewContext, originalMessage: Message) { - await context.panel.webview.postMessage({ - messageType: MessageType.RESPONSE, - command: Command.UNSUPPORTED_COMMAND, - originalMessage, - } as UnsupportedMessage) -} diff --git a/packages/core/src/stepFunctions/workflowStudio/workflowStudioEditor.ts b/packages/core/src/stepFunctions/workflowStudio/workflowStudioEditor.ts index ba719856516..1a31b1d07d4 100644 --- a/packages/core/src/stepFunctions/workflowStudio/workflowStudioEditor.ts +++ b/packages/core/src/stepFunctions/workflowStudio/workflowStudioEditor.ts @@ -8,7 +8,7 @@ import * as vscode from 'vscode' import { telemetry } from '../../shared/telemetry/telemetry' import { i18n } from '../../shared/i18n-helper' import { broadcastFileChange } from './handleMessage' -import { FileWatchInfo, WebviewContext, WorkflowMode } from './types' +import { FileWatchInfo, WebviewContext, WorkflowMode } from '../messageHandlers/types' import { CancellationError } from '../../shared/utilities/timeoutUtils' import { handleMessage } from './handleMessage' import { isInvalidJsonFile } from '../utils' diff --git a/packages/core/src/stepFunctions/workflowStudio/workflowStudioEditorProvider.ts b/packages/core/src/stepFunctions/workflowStudio/workflowStudioEditorProvider.ts index 4cf47e700a5..faf09ca9d38 100644 --- a/packages/core/src/stepFunctions/workflowStudio/workflowStudioEditorProvider.ts +++ b/packages/core/src/stepFunctions/workflowStudio/workflowStudioEditorProvider.ts @@ -15,7 +15,7 @@ import { getTabSizeSetting } from '../../shared/utilities/editorUtilities' import { WorkflowStudioEditor } from './workflowStudioEditor' import { i18n } from '../../shared/i18n-helper' import { isInvalidJsonFile, isInvalidYamlFile } from '../utils' -import { ComponentType, WorkflowMode } from './types' +import { ComponentType, WorkflowMode } from '../messageHandlers/types' import { isLocalDev, localhost, cdn } from '../constants/webviewResources' let clientId = '' diff --git a/packages/core/src/test/stepFunctions/workflowStudio/workflowStudioApiHandler.test.ts b/packages/core/src/test/stepFunctions/apiHandler/stepFunctionApiHandler.test.ts similarity index 90% rename from packages/core/src/test/stepFunctions/workflowStudio/workflowStudioApiHandler.test.ts rename to packages/core/src/test/stepFunctions/apiHandler/stepFunctionApiHandler.test.ts index c16534abc4d..d2f44096874 100644 --- a/packages/core/src/test/stepFunctions/workflowStudio/workflowStudioApiHandler.test.ts +++ b/packages/core/src/test/stepFunctions/apiHandler/stepFunctionApiHandler.test.ts @@ -5,7 +5,7 @@ import assert from 'assert' import sinon from 'sinon' -import { WorkflowStudioApiHandler } from '../../../stepFunctions/workflowStudio/workflowStudioApiHandler' +import { StepFunctionApiHandler } from '../../../stepFunctions/messageHandlers/stepFunctionApiHandler' import { MockDocument } from '../../fake/fakeDocument' import { ApiAction, @@ -13,15 +13,15 @@ import { MessageType, WebviewContext, WorkflowMode, -} from '../../../stepFunctions/workflowStudio/types' +} from '../../../stepFunctions/messageHandlers/types' import * as vscode from 'vscode' import { assertTelemetry } from '../../testUtil' import { StepFunctionsClient } from '../../../shared/clients/stepFunctions' import { IamClient } from '../../../shared/clients/iam' -describe('WorkflowStudioApiHandler', function () { +describe('stepFunctionApiHandler', function () { let postMessageStub: sinon.SinonStub - let apiHandler: WorkflowStudioApiHandler + let apiHandler: StepFunctionApiHandler let testState: sinon.SinonStub async function assertTestApiResponse(expectedResponse: any) { @@ -65,7 +65,7 @@ describe('WorkflowStudioApiHandler', function () { } const sfnClient = new StepFunctionsClient('us-east-1') - apiHandler = new WorkflowStudioApiHandler('us-east-1', context, { + apiHandler = new StepFunctionApiHandler('us-east-1', context, { sfn: sfnClient, iam: new IamClient('us-east-1'), })