Skip to content

Commit

Permalink
feat(stepfunctions): Workflow Studio integration #6544
Browse files Browse the repository at this point in the history
## Problem
1. Currently the indentation setting used in the integration defaults to
4 spaces. If it's different in user's VSCode setting, the spacing for
the string sent from the integration will not match
2. Currently context menu lacks the command to launch WFS. It [has been
added
before](https://github.com/aws/aws-toolkit-vscode/pull/5834/files), but
it looks it was accidentally added to auto-generated file and was thus
removed later
3. WFS editor is opened by default in all modes, including conflict
resolution and some other views
4. Bug: when opening split panel while having WFS integration open, it
opens a panel with custom editor, which stays empty since we already
have an instance of WFS editor launched for that file
5. Bug: If the webview is unavailable (e.g. CDN is down or the user is
offline), integration throws an error modal and the file can't be opened
easily, even in the default editor
6. Bug: when YAML in the local file is invalid, WFS integration is
opened to the file, but since it fails to transform YAML to valid JSON,
it opens with empty definition

## Solution
1. Passing a user setting for tab spacing to be used on the webview side
to format JSON/YAML with the right indentation
2. Adding an option to launch WFS from context menu, as it was added
before (but this time in the right package.json)
3. Add editorAssociation to not open WFS in specific environments that
do not require it
4. When opening split panel panel while having WFS integration open,
open that file in default editor
5. If the webview is unavailable, open file in default editor
6. When YAML in the local file is invalid, open default editor instead
of WFS and show warning message (similar to what we do for invalid JSON)
  • Loading branch information
witness-me authored Feb 10, 2025
1 parent dbfb0b2 commit bf4be27
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 25 deletions.
4 changes: 3 additions & 1 deletion packages/core/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
"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",
"AWS.stepFunctions.workflowStudio.actions.saveSuccessMessage": "{0} has been saved",
"AWS.stepFunctions.workflowStudio.actions.invalidJson": "The Workflow Studio editor was not opened because the JSON in the file is invalid. To access Workflow Studio, please fix the JSON and manually reopen the integration.",
"AWS.stepFunctions.workflowStudio.actions.InvalidJSONContent": "The Workflow Studio editor was not opened because the JSON in the file is invalid. To access Workflow Studio, please fix the JSON and manually reopen the integration.",
"AWS.stepFunctions.workflowStudio.actions.InvalidYAMLContent": "The Workflow Studio editor was not opened because the YAML in the file is invalid. To access Workflow Studio, please fix the YAML and manually reopen the integration.",
"AWS.stepFunctions.workflowStudio.actions.webviewFetchFailed": "Failed to load Workflow Studio editor. Please check your network connection and try again.",
"AWS.configuration.description.awssam.debug.api": "API Gateway configuration",
"AWS.configuration.description.awssam.debug.api.clientCertId": "The API Gateway client certificate ID",
"AWS.configuration.description.awssam.debug.api.headers": "Additional HTTP headers",
Expand Down
25 changes: 21 additions & 4 deletions packages/core/src/stepFunctions/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as nls from 'vscode-nls'
const localize = nls.loadMessageBundle()

import { IAM, StepFunctions } from 'aws-sdk'
import * as yaml from 'js-yaml'
import * as vscode from 'vscode'
import { StepFunctionsClient } from '../shared/clients/stepFunctionsClient'
import { fileExists } from '../shared/filesystemUtilities'
Expand Down Expand Up @@ -232,7 +233,7 @@ export async function isDocumentValid(text: string, textDocument?: vscode.TextDo
}

/**
* Checks if the JSON content in a text document is invalid.
* Checks if the JSON content in an ASL text document is invalid.
* Returns `true` for invalid JSON; `false` for valid JSON, empty content, or non-JSON files.
*
* @param textDocument - The text document to check.
Expand All @@ -241,9 +242,25 @@ export async function isDocumentValid(text: string, textDocument?: vscode.TextDo
export const isInvalidJsonFile = (textDocument: vscode.TextDocument): boolean => {
const documentContent = textDocument.getText().trim()
// An empty file or whitespace-only text is considered valid JSON for our use case
return textDocument.fileName.toLowerCase().endsWith('.json') && documentContent
? isInvalidJson(documentContent)
: false
return textDocument.languageId === 'asl' && documentContent ? isInvalidJson(documentContent) : false
}

/**
* Checks if the YAML content in an ASL text document is invalid.
* Returns `true` for invalid YAML; `false` for valid YAML, empty content, or non-YAML files.
*
* @param textDocument - The text document to check.
* @returns `true` if invalid; `false` otherwise.
*/
export const isInvalidYamlFile = (textDocument: vscode.TextDocument): boolean => {
try {
if (textDocument.languageId === 'asl-yaml') {
yaml.load(textDocument.getText())
}
return false
} catch {
return true
}
}

const isInvalidJson = (content: string): boolean => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ import { telemetry } from '../../shared/telemetry/telemetry'
import globals from '../../shared/extensionGlobals'
import { getRandomString, getStringHash } from '../../shared/utilities/textUtilities'
import { ToolkitError } from '../../shared/errors'
import { getTabSizeSetting } from '../../shared/utilities/editorUtilities'
import { WorkflowStudioEditor } from './workflowStudioEditor'
import { i18n } from '../../shared/i18n-helper'
import { isInvalidJsonFile } from '../utils'
import { isInvalidJsonFile, isInvalidYamlFile } from '../utils'

const isLocalDev = false
const localhost = 'http://127.0.0.1:3002'
Expand Down Expand Up @@ -72,9 +73,6 @@ export class WorkflowStudioEditorProvider implements vscode.CustomTextEditorProv
* @private
*/
private getWebviewContent = async () => {
if (!this.webviewHtml) {
await this.fetchWebviewHtml()
}
let htmlFileSplit = this.webviewHtml.split('<head>')

// Set asset source to CDN
Expand All @@ -86,13 +84,15 @@ export class WorkflowStudioEditorProvider implements vscode.CustomTextEditorProv
const localeTag = `<meta name='locale' content='${locale}'>`
const theme = vscode.window.activeColorTheme.kind
const isDarkMode = theme === vscode.ColorThemeKind.Dark || theme === vscode.ColorThemeKind.HighContrast
const tabSizeTag = `<meta name='tab-size' content='${getTabSizeSetting()}'>`
const darkModeTag = `<meta name='dark-mode' content='${isDarkMode}'>`
let html = `${htmlFileSplit[0]} <head> ${baseTag} ${localeTag} ${darkModeTag} ${htmlFileSplit[1]}`
let html = `${htmlFileSplit[0]} <head> ${baseTag} ${localeTag} ${darkModeTag} ${tabSizeTag} ${htmlFileSplit[1]}`

const nonce = getRandomString()
const localDevURL = isLocalDev ? localhost : ''
htmlFileSplit = html.split("script-src 'self'")

html = `${htmlFileSplit[0]} script-src 'self' 'nonce-${nonce}' ${isLocalDev && localhost} ${htmlFileSplit[1]}`
html = `${htmlFileSplit[0]} script-src 'self' 'nonce-${nonce}' ${localDevURL} ${htmlFileSplit[1]}`
htmlFileSplit = html.split('<body>')

const script = await fs.readFileText(
Expand All @@ -115,35 +115,56 @@ export class WorkflowStudioEditorProvider implements vscode.CustomTextEditorProv
_token: vscode.CancellationToken
): Promise<void> {
await telemetry.stepfunctions_openWorkflowStudio.run(async () => {
// For invalid JSON, open default editor and show warning message
if (isInvalidJsonFile(document)) {
const reopenWithDefaultEditor = async () => {
await vscode.commands.executeCommand('vscode.openWith', document.uri, 'default')
webviewPanel.dispose()
void vscode.window.showWarningMessage(i18n('AWS.stepFunctions.workflowStudio.actions.invalidJson'))
}

const isInvalidJson = isInvalidJsonFile(document)
const isInvalidYaml = isInvalidYamlFile(document)

if (isInvalidJson || isInvalidYaml) {
const language = isInvalidJson ? 'JSON' : 'YAML'
const errorKey = isInvalidJson ? 'InvalidJSONContent' : 'InvalidYAMLContent'

await reopenWithDefaultEditor()
void vscode.window.showWarningMessage(i18n(`AWS.stepFunctions.workflowStudio.actions.${errorKey}`))
throw ToolkitError.chain(
'Invalid JSON file',
'The Workflow Studio editor was not opened because the JSON in the file is invalid',
{
code: 'InvalidJSONContent',
}
`Invalid ${language} file`,
`The Workflow Studio editor was not opened because the ${language} in the file is invalid`,
{ code: errorKey }
)
}

if (!this.webviewHtml) {
await this.fetchWebviewHtml()
try {
await this.fetchWebviewHtml()
} catch (e) {
await reopenWithDefaultEditor()

void vscode.window.showWarningMessage(
i18n('AWS.stepFunctions.workflowStudio.actions.webviewFetchFailed')
)
throw ToolkitError.chain(
'Failed to fetch editor content',
'Could not retrieve content for the Workflow Studio editor',
{
code: 'webviewFetchFailed',
}
)
}
}

if (clientId === '') {
clientId = getClientId(globals.globalState)
}
// Attempt to retrieve existing visualization if it exists.
const existingVisualization = this.managedVisualizations.get(document.uri.fsPath)

const existingVisualization = this.managedVisualizations.get(document.uri.fsPath)
if (existingVisualization) {
existingVisualization.showPanel()
// Prevent opening multiple custom editors for a single file
await reopenWithDefaultEditor()
} else {
// Existing visualization does not exist, construct new visualization
// Construct new visualization
try {
const fileId = getStringHash(`${document.uri.fsPath}${clientId}`)
const newVisualization = new WorkflowStudioEditor(
Expand Down
18 changes: 17 additions & 1 deletion packages/toolkit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1412,6 +1412,11 @@
"command": "aws.openInApplicationComposer",
"when": "isFileSystemResource && !(resourceFilename =~ /^.*\\.tc\\.json$/) && resourceFilename =~ /^.*\\.(json|yml|yaml|template)$/",
"group": "z_aws@1"
},
{
"command": "aws.stepfunctions.openWithWorkflowStudio",
"when": "isFileSystemResource && resourceFilename =~ /^.*\\.asl\\.(json|yml|yaml)$/",
"group": "z_aws@1"
}
],
"view/item/context": [
Expand Down Expand Up @@ -3917,6 +3922,16 @@
}
}
},
{
"command": "aws.stepfunctions.openWithWorkflowStudio",
"title": "%AWS.command.stepFunctions.openWithWorkflowStudio%",
"category": "%AWS.title%",
"cloud9": {
"cn": {
"category": "%AWS.title.cn%"
}
}
},
{
"command": "aws.createNewThreatComposer",
"title": "%AWS.command.threatComposer.createNew%",
Expand Down Expand Up @@ -4654,7 +4669,8 @@
],
"configurationDefaults": {
"workbench.editorAssociations": {
"{git,gitlens,conflictResolution,vscode-local-history}:/**/*.tc.json": "default"
"{git,gitlens,conflictResolution,vscode-local-history}:/**/*.tc.json": "default",
"{git,gitlens,conflictResolution,vscode-local-history}:/**/*.{asl.json,asl.yaml,asl.yml}": "default"
}
}
},
Expand Down

0 comments on commit bf4be27

Please sign in to comment.